/********************************************************************************
 * Copyright (C) 2018 Ericsson and others.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License v. 2.0 are satisfied: GNU General Public License, version 2
 * with the GNU Classpath Exception which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 ********************************************************************************/ import debounce = require ( 'p-debounce' ) ; import { injectable , inject } from 'inversify' ; import { JSONExt , JSONValue } from '@phosphor/coreutils' ; import URI from '../../common/uri' ; import { Disposable , DisposableCollection , Emitter , Event } from '../../common' ; import { Deferred } from '../../common/promise-util' ; import { PreferenceScope } from './preference-scope' ; import { PreferenceLanguageOverrideService } from './preference-language-override-service' ; export interface PreferenceProviderDataChange { readonly preferenceName : string ; readonly newValue ? : any ; readonly oldValue ? : any ; readonly scope : PreferenceScope ; readonly domain ? : string [ ] ; } export namespace PreferenceProviderDataChange { export function affects ( change , resourceUri ? ) { const resourcePath = resourceUri && new URI ( resourceUri ) . path ; const domain = change . domain ; return ! resourcePath || ! domain || domain . some ( uri => new URI ( uri ) . path . relativity ( resourcePath ) >= 0 ) ; } } export interface PreferenceProviderDataChanges { [ preferenceName ] : PreferenceProviderDataChange ; } export interface PreferenceResolveResult < T > { configUri ? : URI ; value ? : T ; } /**
 * The  PreferenceProvider is used to store and retrieve preference values. A  PreferenceProvider does not operate in a global scope but is
 * configured for one or more  PreferenceScope s. The (default implementation for the)  PreferenceService aggregates all  PreferenceProvider s and
 * serves as a common facade for manipulating preference values.
  @ injectable ( ) export abstract class PreferenceProvider implements Disposable { @ inject ( PreferenceLanguageOverrideService ) protected readonly preferenceOverrideService : PreferenceLanguageOverrideService ; protected readonly onDidPreferencesChangedEmitter = new Emitter < PreferenceProviderDataChanges > ( ) ; readonly onDidPreferencesChanged : Event < PreferenceProviderDataChanges > = this . onDidPreferencesChangedEmitter . event ; protected readonly toDispose = new DisposableCollection ( ) ; protected readonly _ready = new Deferred < void > ( ) ; constructor ( ) { this . toDispose . push ( this . onDidPreferencesChangedEmitter ) ; } dispose ( ) { this . toDispose . dispose ( ) ; } protected deferredChanges : PreferenceProviderDataChanges | undefined ; /**
     * Informs the listeners that one or more preferences of this provider are changed.
     * The listeners are able to find what was changed from the emitted event.
     */ protected emitPreferencesChangedEvent ( changes ) { if ( Array . isArray ( changes ) ) { for ( const change of changes ) { this . mergePreferenceProviderDataChange ( change ) ; } } else { for ( const preferenceName of Object . keys ( changes ) ) { this . mergePreferenceProviderDataChange ( changes [ preferenceName ] ) ; } } return this . fireDidPreferencesChanged ( ) ; } protected mergePreferenceProviderDataChange ( change ) { if ( ! this . deferredChanges ) { this . deferredChanges = { } ; } const current = this . deferredChanges [ change . preferenceName ] ; const { newValue , scope , domain } = change ; if ( ! current ) { this . deferredChanges [ change . preferenceName ] = change ; } else if ( current . oldValue === newValue ) { delete this . deferredChanges [ change . preferenceName ] ; } else { Object . assign ( current , { newValue , scope , domain } ) ; } } protected fireDidPreferencesChanged = debounce ( ( ) => { const changes = this . deferredChanges ; this . deferredChanges = undefined ; if ( changes && Object . keys ( changes ) . length ) { this . onDidPreferencesChangedEmitter . fire ( changes ) ; return true ; } return false ; } , 0 ) ; param preferenceName param resourceUri returns get < T > ( preferenceName , resourceUri ? ) { return this . resolve < T > ( preferenceName , resourceUri ) . value ; } param preferenceName param resourceUri returns resolve < T > ( preferenceName , resourceUri ? ) { const value = this . getPreferences ( resourceUri ) [ preferenceName ] ; if ( value !== undefined ) { return { value , configUri : this . getConfigUri ( resourceUri ) } ; } return { } ; } abstract getPreferences ( resourceUri ? ) ; param key param value param resourceUri returns abstract setPreference ( key , value , resourceUri ? ) ; /**
     * Resolved when the preference provider is ready to provide preferences
     * It should be resolved by subclasses.
     */ get ready ( ) : Promise < void > { return this . _ready . promise ; } returns getDomain ( ) { return undefined ; } param resourceUri param sectionName returns getConfigUri ( resourceUri ? , sectionName ? ) { return undefined ; } param resourceUri returns getContainingConfigUri ? ( resourceUri ? , sectionName ? ) ; static merge ( source , target ) { if ( source === undefined || ! JSONExt . isObject ( source ) ) { return JSONExt . deepCopy ( target ) ; } if ( JSONExt . isPrimitive ( target ) ) { return { } ; } for ( const key of Object . keys ( target ) ) { const value = ( target as any ) [ key ] ; if ( key in source ) { if ( JSONExt . isObject ( source [ key ] ) && JSONExt . isObject ( value ) ) { this . merge ( source [ key ] , value ) ; continue ; } } source [ key ] = JSONExt . deepCopy ( value ) ; } return source ; } /**
     * Handles deep equality with the possibility of `undefined`
     */ static deepEqual ( a , b ) { if ( a === b ) { return true ; } if ( a === undefined || b === undefined ) { return false ; } return JSONExt . deepEqual ( a , b ) ; } protected getParsedContent ( jsonData ) { const preferences = { } ; if ( typeof jsonData !== 'object' ) { return preferences ; } for ( const preferenceName in jsonData ) { const preferenceValue = jsonData [ preferenceName ] ; if ( this . preferenceOverrideService . testOverrideValue ( preferenceName , preferenceValue ) ) { for ( const overriddenPreferenceName in preferenceValue ) { const overriddenValue = preferenceValue [ overriddenPreferenceName ] ; preferences [ `${ preferenceName }.${ overriddenPreferenceName }` ] = overriddenValue ; } } else { preferences [ preferenceName ] = preferenceValue ; } } return preferences ; } }