001/*
002 * Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package org.eclipse.golo.cli.command;
012
013import com.beust.jcommander.Parameter;
014import com.beust.jcommander.Parameters;
015import com.beust.jcommander.converters.IParameterSplitter;
016
017import java.io.File;
018import java.net.MalformedURLException;
019import java.net.URL;
020import java.net.URLClassLoader;
021import java.util.List;
022import java.util.LinkedList;
023import java.util.Collections;
024import java.util.Arrays;
025
026import org.eclipse.golo.compiler.GoloClassLoader;
027
028/**
029 * Manage the classpath and initialize the class loader.
030 */
031@Parameters(resourceBundle = "commands")
032public class ClasspathOption {
033
034  /**
035   * The system property to define the path.
036   */
037  public static final String PROPERTY = "golo.class.path";
038
039  /**
040   * The environment variable used to define the path.
041   */
042  public static final String ENV = "GOLOPATH";
043  private static final List<String> DEFAULT = Collections.singletonList(".");
044  private static final String SEP = System.getProperty("path.separator", ":");
045
046  private static class ClasspathSplitter implements IParameterSplitter {
047    private static final String RE = String.format("[,%s]", SEP);
048    public List<String> split(String value) {
049      return Arrays.asList(value.split(RE));
050    }
051  }
052
053  @Parameter(names = "--classpath", variableArity = true, descriptionKey = "classpath", splitter = ClasspathSplitter.class)
054  List<String> classpath = new LinkedList<>();
055
056  private static URLClassLoader primaryClassLoader(List<String> classpath) throws MalformedURLException {
057    URL[] urls = new URL[classpath.size()];
058    for (int i = 0; i < classpath.size(); i++) {
059      urls[i] = new File(classpath.get(i)).toURI().toURL();
060    }
061    return new URLClassLoader(urls);
062  }
063
064  /**
065   * Create a golo classloader using the given classpath list.
066   * <p>
067   * If {@code classpath} is empty, packages from the {@code PROPERTY} system property are used, then from
068   * {@code ENV} environment variable if no property is defined. Finally, the current directory is used if no other path
069   * is defined.
070   * The current thread class loader is set to the created golo class loader.
071   */
072  public static GoloClassLoader initGoloClassLoader(List<String> classpath) throws MalformedURLException {
073    URLClassLoader primaryClassLoader = primaryClassLoader(initClassPath(classpath));
074    GoloClassLoader loader = new GoloClassLoader(primaryClassLoader);
075    Thread.currentThread().setContextClassLoader(loader);
076    return loader;
077  }
078
079  private static List<String> initClassPath(List<String> init) {
080    // priority CLI > property > env > default
081    List<String> classpath = init;
082    if (classpath.isEmpty()) {
083      classpath = getFromEnv(System.getProperty(PROPERTY));
084    }
085    if (classpath.isEmpty()) {
086      classpath = getFromEnv(System.getenv(ENV));
087    }
088    if (classpath.isEmpty()) {
089      // XXX: should "." be used in any case or only if no other path is given?
090      classpath = DEFAULT;
091    }
092    System.setProperty(PROPERTY, String.join(SEP, classpath));
093    return classpath;
094  }
095
096  private static List<String> getFromEnv(String value) {
097    if (value == null) {
098      return Collections.emptyList();
099    }
100    return Arrays.asList(value.split(SEP));
101  }
102
103  /**
104   * Init the class loader using the parsed command line option.
105   */
106  public GoloClassLoader initGoloClassLoader() throws MalformedURLException {
107    return initGoloClassLoader(this.classpath);
108  }
109}