Java tutorial
/* * Copyright 2013 Palantir Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.palantir.typescript.preferences; import static com.google.common.base.Preconditions.checkNotNull; import static com.palantir.typescript.TypeScriptPlugin.logError; import static com.palantir.typescript.TypeScriptPlugin.logInfo; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Objects; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IScopeContext; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Joiner; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.hash.Hashing; import com.google.common.io.CharStreams; import com.google.common.io.Files; import com.palantir.typescript.IPreferenceConstants; import com.palantir.typescript.TypeScriptPlugin; import com.palantir.typescript.TypeScriptProjectSources; /** * tsconfig file reader which holds mapping with TypeScript plugin preferences. * * @author lgrignon */ public final class TsConfigPreferences { private static final BiMap<String, String> PREFERENCE_TO_TSCONFIG_PATH = createPreferenceToTsConfigPathMap(); private static final Map<String, String> TSCONFIG_PATH_TO_PREFERENCE = PREFERENCE_TO_TSCONFIG_PATH.inverse(); private static final Map<String, String> TSCONFIG_TARGET_MAPPING = ImmutableMap.of("ES3", "ECMASCRIPT3", "ES5", "ECMASCRIPT5", "ES6", "ECMASCRIPT6", "ES2015", "ECMASCRIPT6"); private static final Map<String, String> TSCONFIG_MODULE_KIND_MAPPING = ImmutableMap.of("COMMONJS", "COMMONSJS", "ES2015", "ES6"); private static final Map<String, String> TSCONFIG_MODULE_RESOLUTION_MAPPING = ImmutableMap.of("NODEJS", "NODE_JS"); private final IProject project; private Map<String, String> preferenceValues; public TsConfigPreferences(IProject project) { checkNotNull(project); this.project = project; } public synchronized Map<String, String> getPreferenceValues() { if (preferenceValues == null || didTsConfigFileChanged()) { reloadTsConfigFile(); } return Collections.unmodifiableMap(preferenceValues); } public String getValue(String preferenceName) { return getPreferenceValues().get(preferenceName); } public boolean reloadTsConfigFile() { logInfo("reload tsconfig file"); this.preferenceValues = Maps.newHashMap(); IFile tsConfigFile = null; InputStream tsConfigStream = null; boolean loaded = false; try { tsConfigFile = getTsConfigFile(); if (tsConfigFile.exists()) { if (!tsConfigFile.isSynchronized(IResource.DEPTH_ZERO)) { tsConfigFile.refreshLocal(IResource.DEPTH_ZERO, null); } // refresh tsconfig cache infos IEclipsePreferences projectPreferences = this.getProjectPreferences(); projectPreferences.put(IPreferenceConstants.PREFERENCE_STORE_TS_CONFIG_HASH, getFileSHA1(tsConfigFile)); projectPreferences.putLong(IPreferenceConstants.PREFERENCE_STORE_TS_CONFIG_LAST_MODIFICATION_TIME, tsConfigFile.getModificationStamp()); // read tsconfig JSON content tsConfigStream = tsConfigFile.getContents(); String tsConfigContent = CharStreams .toString(new InputStreamReader(tsConfigStream, tsConfigFile.getCharset())); // convert tsconfig JSON to Map ObjectMapper mapper = new ObjectMapper(); Map<String, Object> tsConfigEntries = mapper.readValue(tsConfigContent, new TypeReference<Map<String, Object>>() { }); // resets preferences to default resetTsConfigDefaultPreferences(); // reads preferences from tsconfig decodeTsConfigEntries("", tsConfigEntries); loaded = true; } } catch (Exception e) { logError("Cannot reload ts config file '" + tsConfigFile + "'", e); } finally { if (tsConfigStream != null) { try { tsConfigStream.close(); } catch (IOException e) { System.err.println("error while releasing tsconfig stream"); } } } return loaded; } public boolean isTsConfigPreference(String preferenceName) { return PREFERENCE_TO_TSCONFIG_PATH.containsKey(preferenceName); } private String getFileSHA1(IFile tsConfigFile) throws IOException { return Files.hash(tsConfigFile.getRawLocation().toFile(), Hashing.sha1()).toString(); } private void resetTsConfigDefaultPreferences() { preferenceValues.clear(); for (String preferenceKey : PREFERENCE_TO_TSCONFIG_PATH.keySet()) { preferenceValues.put(preferenceKey, null); } System.out.println("tsconfig preferences reset: " + this); } private void decodeTsConfigEntries(String jsonTreePath, Map<String, Object> entries) { for (Map.Entry<String, Object> tsConfigEntry : entries.entrySet()) { if (tsConfigEntry.getValue() instanceof Map) { decodeTsConfigEntries(jsonTreePath + tsConfigEntry.getKey() + ".", (Map) tsConfigEntry.getValue()); } else if (isSupportedTsConfigPath(jsonTreePath + tsConfigEntry.getKey())) { String matchingPreference = TSCONFIG_PATH_TO_PREFERENCE.get(jsonTreePath + tsConfigEntry.getKey()); String value = tsConfigValueToPreferenceValue(tsConfigEntry.getValue(), matchingPreference); logInfo("setting preference " + matchingPreference + " to " + value); this.preferenceValues.put(matchingPreference, value); } } } private String tsConfigValueToPreferenceValue(Object tsConfigValue, String matchingPreference) { String value = null; if (tsConfigValue != null) { if (tsConfigValue instanceof Collection) { value = Joiner.on(TypeScriptProjectSources.BUILD_PATH_SPEC_SEPARATOR) .join((Collection) tsConfigValue); } else { value = tsConfigValue.toString(); } // TODO : awful mapping, we should rename our enums to match standard options / tsconfig values if (matchingPreference.equals(IPreferenceConstants.COMPILER_TARGET)) { value = value.toUpperCase(); if (TSCONFIG_TARGET_MAPPING.containsKey(value)) { value = TSCONFIG_TARGET_MAPPING.get(value); } } else if (matchingPreference.equals(IPreferenceConstants.COMPILER_MODULE)) { value = value.toUpperCase(); if (TSCONFIG_MODULE_KIND_MAPPING.containsKey(value)) { value = TSCONFIG_MODULE_KIND_MAPPING.get(value); } } else if (matchingPreference.equals(IPreferenceConstants.COMPILER_MODULE_RESOLUTION)) { value = value.toUpperCase(); if (TSCONFIG_MODULE_RESOLUTION_MAPPING.containsKey(value)) { value = TSCONFIG_MODULE_RESOLUTION_MAPPING.get(value); } } else if (matchingPreference.equals(IPreferenceConstants.COMPILER_JSX)) { value = value.toUpperCase(); } } return value; } private boolean isSupportedTsConfigPath(String tsConfigJsonPath) { return TSCONFIG_PATH_TO_PREFERENCE.containsKey(tsConfigJsonPath); } private boolean didTsConfigFileChanged() { IEclipsePreferences projectPreferences = this.getProjectPreferences(); IFile tsConfigFile = getTsConfigFile(); boolean changed = true; if (tsConfigFile.exists()) { changed = false; try { long previousLastModTimestamp = projectPreferences .getLong(IPreferenceConstants.PREFERENCE_STORE_TS_CONFIG_LAST_MODIFICATION_TIME, -1); long currentLastModTimestamp = tsConfigFile.getModificationStamp(); if (previousLastModTimestamp != currentLastModTimestamp) { // optimization: get hash only if mod timestamp changed String previousHash = projectPreferences .get(IPreferenceConstants.PREFERENCE_STORE_TS_CONFIG_HASH, null); String currentHash = getFileSHA1(tsConfigFile); if (!Objects.equals(previousHash, currentHash)) { changed = true; } } } catch (Exception e) { logError("Cannot tell if tsconfig file changed '" + tsConfigFile + "'", e); } } return changed; } private IFile getTsConfigFile() { IFile tsConfigFile = this.project.getFile("tsconfig.json"); return tsConfigFile; } private IEclipsePreferences getProjectPreferences() { IScopeContext projectScope = new ProjectScope(this.project); return projectScope.getNode(TypeScriptPlugin.ID); } private static BiMap<String, String> createPreferenceToTsConfigPathMap() { BiMap<String, String> map = HashBiMap.<String, String>create(); map.put(IPreferenceConstants.BUILD_PATH_FILES, "files"); map.put(IPreferenceConstants.BUILD_PATH_INCLUDE, "include"); map.put(IPreferenceConstants.BUILD_PATH_EXCLUDE, "exclude"); map.put(IPreferenceConstants.COMPILER_COMPILE_ON_SAVE, "compileOnSave"); map.put(IPreferenceConstants.COMPILER_DECLARATION, "compilerOptions.declaration"); map.put(IPreferenceConstants.COMPILER_EXPERIMENTAL_DECORATORS, "compilerOptions.experimentalDecorators"); map.put(IPreferenceConstants.COMPILER_EMIT_DECORATOR_METADATA, "compilerOptions.emitDecoratorMetadata"); map.put(IPreferenceConstants.COMPILER_INLINE_SOURCE_MAP, "compilerOptions.inlineSourceMap"); map.put(IPreferenceConstants.COMPILER_INLINE_SOURCES, "compilerOptions.inlineSource"); map.put(IPreferenceConstants.COMPILER_JSX, "compilerOptions.jsx"); map.put(IPreferenceConstants.COMPILER_MODULE, "compilerOptions.module"); map.put(IPreferenceConstants.COMPILER_MODULE_RESOLUTION, "compilerOptions.moduleResolution"); map.put(IPreferenceConstants.COMPILER_NO_EMIT_ON_ERROR, "compilerOptions.noEmitOnError"); map.put(IPreferenceConstants.COMPILER_NO_FALLTHROUGH_CASES_IN_SWITCH, "compilerOptions.noFallthroughCasesInSwitch"); map.put(IPreferenceConstants.COMPILER_NO_IMPLICIT_ANY, "compilerOptions.noImplicitAny"); map.put(IPreferenceConstants.COMPILER_NO_IMPLICIT_RETURNS, "compilerOptions.noImplicitReturns"); map.put(IPreferenceConstants.COMPILER_NO_LIB, "compilerOptions.noLib"); map.put(IPreferenceConstants.COMPILER_OUT_DIR, "compilerOptions.outDir"); map.put(IPreferenceConstants.COMPILER_OUT_FILE, "compilerOptions.outFile"); map.put(IPreferenceConstants.COMPILER_REMOVE_COMMENTS, "compilerOptions.removeComments"); map.put(IPreferenceConstants.COMPILER_SOURCE_MAP, "compilerOptions.sourceMap"); map.put(IPreferenceConstants.COMPILER_SUPPRESS_EXCESS_PROPERTY_ERRORS, "compilerOptions.suppressExcessPropertyErrors"); map.put(IPreferenceConstants.COMPILER_SUPPRESS_IMPLICIT_ANY_INDEX_ERRORS, "compilerOptions.suppressImplicitAnyIndexErrors"); map.put(IPreferenceConstants.COMPILER_TARGET, "compilerOptions.target"); return map; } @Override public String toString() { return getClass().getSimpleName() + ": " + getPreferenceValues(); } }