Java tutorial
/* * Copyright (C) 2012 Google 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.google.caliper.config; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.google.caliper.api.ResultProcessor; import com.google.caliper.config.VmConfig.Builder; import com.google.caliper.util.Util; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.base.Strings; import com.google.common.collect.BiMap; import com.google.common.collect.Collections2; import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.File; import java.lang.management.ManagementFactory; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; /** * Represents caliper configuration. By default, {@code ~/.caliper/config.properties} and * {@code global-config.properties}. * * @author gak@google.com (Gregory Kick) */ public final class CaliperConfig { @VisibleForTesting final ImmutableMap<String, String> properties; private final ImmutableMap<Class<? extends ResultProcessor>, ResultProcessorConfig> resultProcessorConfigs; @VisibleForTesting public CaliperConfig(ImmutableMap<String, String> properties) throws InvalidConfigurationException { this.properties = checkNotNull(properties); this.resultProcessorConfigs = findResultProcessorConfigs(subgroupMap(properties, "results")); } private static final Pattern CLASS_PROPERTY_PATTERN = Pattern.compile("(\\w+)\\.class"); private static <T> ImmutableBiMap<String, Class<? extends T>> mapGroupNamesToClasses( ImmutableMap<String, String> groupProperties, Class<T> type) throws InvalidConfigurationException { BiMap<String, Class<? extends T>> namesToClasses = HashBiMap.create(); for (Entry<String, String> entry : groupProperties.entrySet()) { Matcher matcher = CLASS_PROPERTY_PATTERN.matcher(entry.getKey()); if (matcher.matches() && !entry.getValue().isEmpty()) { try { Class<?> someClass = Class.forName(entry.getValue()); checkState(type.isAssignableFrom(someClass)); @SuppressWarnings("unchecked") Class<? extends T> verifiedClass = (Class<? extends T>) someClass; namesToClasses.put(matcher.group(1), verifiedClass); } catch (ClassNotFoundException e) { throw new InvalidConfigurationException( "Cannot find result processor class: " + entry.getValue()); } } } return ImmutableBiMap.copyOf(namesToClasses); } private static ImmutableMap<Class<? extends ResultProcessor>, ResultProcessorConfig> findResultProcessorConfigs( ImmutableMap<String, String> resultsProperties) throws InvalidConfigurationException { ImmutableBiMap<String, Class<? extends ResultProcessor>> processorToClass = mapGroupNamesToClasses( resultsProperties, ResultProcessor.class); ImmutableMap.Builder<Class<? extends ResultProcessor>, ResultProcessorConfig> builder = ImmutableMap .builder(); for (Entry<String, Class<? extends ResultProcessor>> entry : processorToClass.entrySet()) { builder.put(entry.getValue(), getResultProcessorConfig(resultsProperties, entry.getKey())); } return builder.build(); } public ImmutableMap<String, String> properties() { return properties; } /** * Returns the configuration of the current host JVM (including the flags used to create it). Any * args specified using {@code vm.args} will also be applied */ public VmConfig getDefaultVmConfig() { return new Builder(new File(System.getProperty("java.home"))).addAllOptions(Collections2 .filter(ManagementFactory.getRuntimeMXBean().getInputArguments(), new Predicate<String>() { @Override public boolean apply(@Nullable String input) { // Exclude the -agentlib:jdwp param which configures the socket debugging protocol. // If this is set in the parent VM we do not want it to be inherited by the child // VM. If it is, the child will die immediately on startup because it will fail to // bind to the debug port (because the parent VM is already bound to it). return !input.startsWith("-agentlib:jdwp"); } })) // still incorporate vm.args .addAllOptions(getArgs(subgroupMap(properties, "vm"))).build(); } public VmConfig getVmConfig(String name) throws InvalidConfigurationException { checkNotNull(name); ImmutableMap<String, String> vmGroupMap = subgroupMap(properties, "vm"); ImmutableMap<String, String> vmMap = subgroupMap(vmGroupMap, name); File homeDir = getJdkHomeDir(vmGroupMap.get("baseDirectory"), vmMap.get("home"), name); return new VmConfig.Builder(homeDir).addAllOptions(getArgs(vmGroupMap)).addAllOptions(getArgs(vmMap)) .build(); } private static final Pattern INSTRUMENT_CLASS_PATTERN = Pattern.compile("([^\\.]+)\\.class"); public ImmutableSet<String> getConfiguredInstruments() { ImmutableSet.Builder<String> resultBuilder = ImmutableSet.builder(); for (String key : subgroupMap(properties, "instrument").keySet()) { Matcher matcher = INSTRUMENT_CLASS_PATTERN.matcher(key); if (matcher.matches()) { resultBuilder.add(matcher.group(1)); } } return resultBuilder.build(); } public InstrumentConfig getInstrumentConfig(String name) { checkNotNull(name); ImmutableMap<String, String> instrumentGroupMap = subgroupMap(properties, "instrument"); ImmutableMap<String, String> insrumentMap = subgroupMap(instrumentGroupMap, name); @Nullable String className = insrumentMap.get("class"); checkArgument(className != null, "no instrument configured named %s", name); return new InstrumentConfig.Builder().className(className) .addAllOptions(subgroupMap(insrumentMap, "options")).build(); } public ImmutableSet<Class<? extends ResultProcessor>> getConfiguredResultProcessors() { return resultProcessorConfigs.keySet(); } public ResultProcessorConfig getResultProcessorConfig(Class<? extends ResultProcessor> resultProcessorClass) { return resultProcessorConfigs.get(resultProcessorClass); } private static ResultProcessorConfig getResultProcessorConfig(ImmutableMap<String, String> resultsProperties, String name) { ImmutableMap<String, String> resultsMap = subgroupMap(resultsProperties, name); return new ResultProcessorConfig.Builder().className(resultsMap.get("class")) .addAllOptions(subgroupMap(resultsMap, "options")).build(); } @Override public String toString() { return Objects.toStringHelper(this).add("properties", properties).toString(); } private static final ImmutableMap<String, String> subgroupMap(ImmutableMap<String, String> map, String groupName) { return Util.prefixedSubmap(map, groupName + "."); } private static List<String> getArgs(Map<String, String> properties) { String argsString = Strings.nullToEmpty(properties.get("args")); ImmutableList.Builder<String> args = ImmutableList.builder(); StringBuilder arg = new StringBuilder(); for (int i = 0; i < argsString.length(); i++) { char c = argsString.charAt(i); switch (c) { case '\\': arg.append(argsString.charAt(++i)); break; case ' ': if (arg.length() > 0) { args.add(arg.toString()); } arg = new StringBuilder(); break; default: arg.append(c); break; } } if (arg.length() > 0) { args.add(arg.toString()); } return args.build(); } // TODO(gak): check that the directory seems to be a jdk home (with a java binary and all of that) // TODO(gak): make this work with different directory layouts. I'm looking at you OS X... private static File getJdkHomeDir(@Nullable String baseDirectoryPath, @Nullable String homeDirPath, String vmConfigName) throws InvalidConfigurationException { if (homeDirPath == null) { File baseDirectory = getBaseDirectory(baseDirectoryPath); File homeDir = new File(baseDirectory, vmConfigName); checkConfiguration(homeDir.isDirectory(), "%s is not a directory", homeDir); return homeDir; } else { File potentialHomeDir = new File(homeDirPath); if (potentialHomeDir.isAbsolute()) { checkConfiguration(potentialHomeDir.isDirectory(), "%s is not a directory", potentialHomeDir); return potentialHomeDir; } else { File baseDirectory = getBaseDirectory(baseDirectoryPath); File homeDir = new File(baseDirectory, homeDirPath); checkConfiguration(homeDir.isDirectory(), "%s is not a directory", potentialHomeDir); return homeDir; } } } private static File getBaseDirectory(@Nullable String baseDirectoryPath) throws InvalidConfigurationException { if (baseDirectoryPath == null) { throw new InvalidConfigurationException("must set either a home directory or a base directory"); } else { File baseDirectory = new File(baseDirectoryPath); checkConfiguration(baseDirectory.isAbsolute(), "base directory cannot be a relative path"); checkConfiguration(baseDirectory.isDirectory(), "base directory must be a directory"); return baseDirectory; } } private static void checkConfiguration(boolean check, String message) throws InvalidConfigurationException { if (!check) { throw new InvalidConfigurationException(message); } } private static void checkConfiguration(boolean check, String messageFormat, Object... args) throws InvalidConfigurationException { if (!check) { throw new InvalidConfigurationException(String.format(messageFormat, args)); } } }