Java tutorial
/* * Copyright (C) 2010-2014, Danilo Pianini and contributors * listed in the project's pom.xml file. * * This file is part of Alchemist, and is distributed under the terms of * the GNU General Public License, with a linking exception, as described * in the file LICENSE in the Alchemist distribution's top directory. */ package it.unibo.alchemist.language.protelis.util; import it.unibo.alchemist.language.protelis.AlignedMap; import it.unibo.alchemist.language.protelis.All; import it.unibo.alchemist.language.protelis.BinaryOp; import it.unibo.alchemist.language.protelis.Constant; import it.unibo.alchemist.language.protelis.CreateTuple; import it.unibo.alchemist.language.protelis.CreateVar; import it.unibo.alchemist.language.protelis.DotOperator; import it.unibo.alchemist.language.protelis.Dt; import it.unibo.alchemist.language.protelis.Eval; import it.unibo.alchemist.language.protelis.FunctionCall; import it.unibo.alchemist.language.protelis.FunctionDefinition; import it.unibo.alchemist.language.protelis.HoodCall; import it.unibo.alchemist.language.protelis.If; import it.unibo.alchemist.language.protelis.MethodCall; import it.unibo.alchemist.language.protelis.NBRCall; import it.unibo.alchemist.language.protelis.NBRRange; import it.unibo.alchemist.language.protelis.NumericConstant; import it.unibo.alchemist.language.protelis.ProtelisStandaloneSetup; import it.unibo.alchemist.language.protelis.Random; import it.unibo.alchemist.language.protelis.RepCall; import it.unibo.alchemist.language.protelis.Self; import it.unibo.alchemist.language.protelis.TernaryOp; import it.unibo.alchemist.language.protelis.UnaryOp; import it.unibo.alchemist.language.protelis.Variable; import it.unibo.alchemist.language.protelis.datatype.Field; import it.unibo.alchemist.language.protelis.interfaces.AnnotatedTree; import it.unibo.alchemist.language.protelis.java7.util.L; import it.unibo.alchemist.language.protelis.protelis.Assignment; import it.unibo.alchemist.language.protelis.protelis.Block; import it.unibo.alchemist.language.protelis.protelis.BooleanVal; import it.unibo.alchemist.language.protelis.protelis.Declaration; import it.unibo.alchemist.language.protelis.protelis.DoubleVal; import it.unibo.alchemist.language.protelis.protelis.Expression; import it.unibo.alchemist.language.protelis.protelis.FunctionDef; import it.unibo.alchemist.language.protelis.protelis.Import; import it.unibo.alchemist.language.protelis.protelis.Program; import it.unibo.alchemist.language.protelis.protelis.RepInitialize; import it.unibo.alchemist.language.protelis.protelis.Statement; import it.unibo.alchemist.language.protelis.protelis.StringVal; import it.unibo.alchemist.language.protelis.protelis.TupleVal; import it.unibo.alchemist.language.protelis.protelis.VAR; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.math3.util.Pair; import org.danilopianini.lang.util.FasterString; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.Resource.Diagnostic; import org.eclipse.xtext.common.types.JvmOperation; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.XtextResourceSet; import org.eclipse.xtext.util.StringInputStream; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import com.google.inject.Injector; /** * @author Danilo Pianini * */ public final class ProtelisLoader { private static final AtomicInteger IDGEN = new AtomicInteger(); private static final XtextResourceSet XTEXT = createResourceSet(); private static final Pattern REGEX_PROTELIS_MODULE = Pattern.compile("(?:\\w+:)*\\w+"); private static final Pattern REGEX_PROTELIS_IMPORT = Pattern.compile("import\\s+((?:\\w+:)*\\w+)\\s+", Pattern.DOTALL); private static final PathMatchingResourcePatternResolver RESOLVER = new PathMatchingResourcePatternResolver(); private static final String PROTELIS_FILE_EXTENSION = "pt"; private static final String UNCHECKED = "unchecked"; private static final String ASSIGNMENT_NAME = "="; private static final String DOT_NAME = "."; private static final String REP_NAME = "rep"; private static final String IF_NAME = "if"; private static final String DT_NAME = "dt"; private static final String PI_NAME = "pi"; private static final String E_NAME = "e"; private static final String RAND_NAME = "random"; private static final String LAMBDA_NAME = "->"; private static final String SELF_NAME = "self"; private static final String EVAL_NAME = "eval"; private static final String NBR_NAME = "nbr"; private static final String NBR_RANGE = "nbrRange"; private static final String ALIGNED_MAP = "alignedMap"; private static final String MUX_NAME = "mux"; private static final String HOOD_END = "Hood"; private static final List<String> BINARY_OPERATORS = initBinaryOperators(); private static final List<String> UNARY_OPERATORS = initUnaryOperators(); private static List<String> initBinaryOperators() { List<String> list = new ArrayList<>(); for (Op2 op2 : Op2.values()) { list.add(op2.toString()); } return list; } private static List<String> initUnaryOperators() { List<String> list = new ArrayList<>(); for (Op1 op1 : Op1.values()) { list.add(op1.toString()); } return list; } private ProtelisLoader() { } /** * @param program * Protelis module, program file or program to execute. It must * be one of: * * i) a valid Protelis qualifier name (Java like name, colon * separated); * * ii) a valid {@link URI} string; * * iii) a valid Protelis program. * * Those possibilities are checked in order. * * The URI String can be in the form of a URL like * "file:///home/user/protelis/myProgram" or a location relative * to the classpath. In case, for instance, * "/my/package/myProgram.pt" is passed, it will be automatically * get converted to "classpath:/my/package/myProgram.pt". All the * Protelis modules your program relies upon must be included in * your Java classpath. The Java classpath scanning is done * automatically by this constructor, linking is performed by * Xtext transparently. {@link URI}s of type "platform:/" are * supported, for those who work within an Eclipse environment. * @return a {@link Pair} of {@link AnnotatedTree} (the program) and * {@link FunctionDefinition} (containing the available functions) */ public static IProgram parse(final String program) { try { if (REGEX_PROTELIS_MODULE.matcher(program).matches()) { return parseURI("classpath:/" + program.replace(':', '/') + "." + PROTELIS_FILE_EXTENSION); } return parseURI(program); } catch (Exception e) { return parseAnonymousModule(program); } } public static IProgram parseAnonymousModule(final String program) { return parse(resourceFromString(program)); } /** * @param programURI * Protelis program file to execute. It must be a either a valid * {@link URI} string, for instance * "file:///home/user/protelis/myProgram" or a location relative * to the classpath. In case, for instance, * "/my/package/myProgram.pt" is passed, it will be automatically * get converted to "classpath:/my/package/myProgram.pt". All the * Protelis modules your program relies upon must be included in * your Java classpath. The Java classpath scanning is done * automatically by this constructor, linking is performed by * Xtext transparently. {@link URI}s of type "platform:/" are * supported, for those who work within an Eclipse environment. * @return a new {@link IProgram} * @throws IOException */ public static IProgram parseURI(final String programURI) throws IOException { return parse(resourceFromURIString(programURI)); } private static Resource resourceFromURIString(final String programURI) throws IOException { loadResourcesRecursively(XTEXT, programURI); final String realURI = (programURI.startsWith("/") ? "classpath:" : "") + programURI; final URI uri = URI.createURI(realURI); return XTEXT.getResource(uri, true); } private static void loadResourcesRecursively(final XtextResourceSet target, final String programURI) throws IOException { loadResourcesRecursively(target, programURI, (Set) new LinkedHashSet<>()); } private static void loadResourcesRecursively(final XtextResourceSet target, final String programURI, final Set<String> alreadyInQueue) throws IOException { final String realURI = (programURI.startsWith("/") ? "classpath:" : "") + programURI; if (!alreadyInQueue.contains(realURI)) { alreadyInQueue.add(realURI); final URI uri = URI.createURI(realURI); final org.springframework.core.io.Resource protelisFile = RESOLVER.getResource(realURI); final InputStream is = protelisFile.getInputStream(); final String ss = IOUtils.toString(is, "UTF-8"); final Matcher matcher = REGEX_PROTELIS_IMPORT.matcher(ss); while (matcher.find()) { final int start = matcher.start(1); final int end = matcher.end(1); final String imp = ss.substring(start, end); final String classpathResource = "classpath:/" + imp.replace(":", "/") + "." + PROTELIS_FILE_EXTENSION; loadResourcesRecursively(target, classpathResource, alreadyInQueue); } target.getResource(uri, true); } } /** * @param program * the program in String format * @return a dummy:/ resource that can be used to interpret the program */ public static Resource resourceFromString(final String program) { final XtextResourceSet xrs = createResourceSet(); final Resource r = xrs.createResource( URI.createURI("dummy:/protelis-generated-program-" + IDGEN.getAndIncrement() + ".pt")); final InputStream in = new StringInputStream(program); try { r.load(in, xrs.getLoadOptions()); } catch (IOException e) { L.error("I/O error while reading in RAM: this must be tough."); } return r; } private static XtextResourceSet createResourceSet() { new org.eclipse.emf.mwe.utils.StandaloneSetup().setPlatformUri("."); final Injector guiceInjector = new ProtelisStandaloneSetup().createInjectorAndDoEMFRegistration(); final XtextResourceSet xtext = guiceInjector.getInstance(XtextResourceSet.class); xtext.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE); return xtext; } /** * @param resource * the {@link Resource} containing the program to execute * @return a {@link Pair} of {@link AnnotatedTree} (the program) and * {@link FunctionDefinition} (containing the available functions) */ public static IProgram parse(final Resource resource) { if (!resource.getErrors().isEmpty()) { for (final Diagnostic d : resource.getErrors()) { final StringBuilder b = new StringBuilder("Error at line "); b.append(d.getLine()); b.append(": "); b.append(d.getMessage()); L.error(b.toString()); } throw new IllegalArgumentException("Your program has syntax errors. Feed me something usable, please."); } final Program root = (Program) resource.getContents().get(0); /* * Create the function headers. * * 1) Take all the imports in reverse order (the first declared is the * one actually declared in case of name conflict), insert them with the * two possible names (fully qualified and short). * This operation must be recursive (for dealing with imports of * imports). * * 2) Override conflicting names with local names */ final Map<FasterString, FunctionDefinition> nameToFun = new LinkedHashMap<>(); final Map<FunctionDef, FunctionDefinition> funToFun = new LinkedHashMap<>(); recursivelyInitFunctions(root, nameToFun, funToFun); /* * Function definitions are in place, now create function bodies. Bodies * may contain lambdas, lambdas are named using processing order, as a * consequence function bodies must be evaluated sequentially. */ final AtomicInteger id = new AtomicInteger(); for (Entry<FunctionDef, FunctionDefinition> e : funToFun.entrySet()) { e.getValue().setBody((AnnotatedTree<?>) parseBlock(e.getKey().getBody(), nameToFun, funToFun, id)); } /* * Create the main program */ return new SimpleProgramImpl(root, parseBlock(root.getProgram(), nameToFun, funToFun, id), nameToFun); } private static void recursivelyInitFunctions(final Program module, final Map<FasterString, FunctionDefinition> nameToFun, final Map<FunctionDef, FunctionDefinition> funToFun) { recursivelyInitFunctions(module, nameToFun, funToFun, (Set) new HashSet<>(), true); } private static void recursivelyInitFunctions(final Program module, final Map<FasterString, FunctionDefinition> nameToFun, final Map<FunctionDef, FunctionDefinition> funToFun, final Set<Program> completed, final boolean initPrivate) { if (!completed.contains(module)) { completed.add(module); /* * Init imports functions, in reverse order */ final EList<Import> imports = module.getProtelisImport(); for (int i = imports.size() - 1; i >= 0; i--) { final Import protelisImport = imports.get(i); recursivelyInitFunctions(protelisImport.getModule(), nameToFun, funToFun, completed, false); } /* * Init local functions */ for (FunctionDef fd : module.getDefinitions()) { if (initPrivate || fd.isPublic()) { final String fname = fd.getName(); final String fullName = module.getName() + ":" + fname; final FasterString ffname = new FasterString(fname); final FasterString ffullName = new FasterString(fullName); final FunctionDefinition def = new FunctionDefinition(ffullName, extractArgs(fd)); nameToFun.put(ffullName, def); nameToFun.put(ffname, def); funToFun.put(fd, def); } } } } private static <T> AnnotatedTree<?> parseBlock(final Block e, final Map<FasterString, FunctionDefinition> nameToFun, final Map<FunctionDef, FunctionDefinition> funToFun, final AtomicInteger id) { final AnnotatedTree<?> first = parseStatement(e.getFirst(), nameToFun, funToFun, id); Block next = e.getOthers(); final List<AnnotatedTree<?>> statements = new LinkedList<>(); statements.add(first); for (; next != null; next = next.getOthers()) { statements.add(parseStatement(next.getFirst(), nameToFun, funToFun, id)); } return new All(statements); } private static <T> AnnotatedTree<?> parseStatement(final Statement e, final Map<FasterString, FunctionDefinition> nameToFun, final Map<FunctionDef, FunctionDefinition> funToFun, final AtomicInteger id) { if (e instanceof Expression) { return parseExpression((Expression) e, nameToFun, funToFun, id); } if (e instanceof Declaration || e instanceof Assignment) { String name; final boolean isAssignment = e.getName().equals(ASSIGNMENT_NAME); if (isAssignment) { name = ((Assignment) e).getRefVar().getName(); } else { name = e.getName(); } return new CreateVar(name, parseExpression(e.getRight(), nameToFun, funToFun, id), !isAssignment); } throw new NotImplementedException("Implement support for nodes of type: " + e.getClass()); } private static MethodCall parseMethod(final JvmOperation jvmOp, final List<Expression> args, final Map<FasterString, FunctionDefinition> nameToFun, final Map<FunctionDef, FunctionDefinition> funToFun, final AtomicInteger id) { final boolean ztatic = jvmOp.isStatic(); final List<AnnotatedTree<?>> arguments = parseArgs(args, nameToFun, funToFun, id); final String classname = jvmOp.getDeclaringType().getQualifiedName(); try { final Class<?> clazz = Class.forName(classname); /* * TODO: Check for return type and params: if param is Field and * return type is not then L.warn() */ List<Method> tempList = new ArrayList<>(); for (Method m : clazz.getMethods()) { if (ztatic) { if (Modifier.isStatic(m.getModifiers())) { tempList.add(m); } } } /* * Same number of arguments */ final int parameterCount = jvmOp.getParameters().size(); List<Method> tempList2 = new ArrayList<>(); for (Method m : tempList) { // TODO: Workaround for different Method API if (m.getParameterTypes().length == parameterCount) { tempList2.add(m); } } /* * Same name */ final String methodName = jvmOp.getSimpleName(); List<Method> res = new ArrayList<>(); for (Method m : tempList2) { if (m.getName().equals(methodName)) { res.add(m); } } /* * There should be only one left - otherwise we have overloading, * and to properly deal with that we need type checking. The * following collection operation is for debug and warning purposes, * and should be removed as soon as we have a proper way to deal * with overloading in place. TODO */ if (res.size() > 1) { final StringBuilder sb = new StringBuilder(64); sb.append("Method "); sb.append(jvmOp.getQualifiedName()); sb.append('/'); sb.append(parameterCount); sb.append(" is overloaded by:\n"); for (Method m : res) { sb.append(m.toString()); sb.append('\n'); // NOPMD } sb.append("Protelis can not (yet) properly deal with that."); L.warn(sb.toString()); } if (res.isEmpty()) { throw new IllegalStateException("Can not bind any method that satisfies the name " + jvmOp.getQualifiedName() + "/" + parameterCount + "."); } return new MethodCall(res.get(0), arguments, ztatic); } catch (ClassNotFoundException e) { throw new IllegalStateException("Class " + classname + " could not be found in classpath."); } catch (SecurityException e) { throw new IllegalStateException( "Class " + classname + " could not be loaded due to security permissions."); } catch (Error e) { throw new IllegalStateException("An error occured while loading class " + classname + "."); } } private static FunctionCall parseFunction(final FunctionDef f, final List<Expression> args, final Map<FasterString, FunctionDefinition> nameToFun, final Map<FunctionDef, FunctionDefinition> funToFun, final AtomicInteger id) { final FunctionDefinition fun = funToFun.get(f); Objects.requireNonNull(fun); final List<AnnotatedTree<?>> arguments = parseArgs(args, nameToFun, funToFun, id); return new FunctionCall(fun, arguments); } private static List<AnnotatedTree<?>> parseArgs(final List<Expression> args, final Map<FasterString, FunctionDefinition> nameToFun, final Map<FunctionDef, FunctionDefinition> funToFun, final AtomicInteger id) { List<AnnotatedTree<?>> list = new ArrayList<>(); for (Expression e : args) { list.add(parseExpression(e, nameToFun, funToFun, id)); } return list; } private static <T> AnnotatedTree<?> parseExpression(final Expression e, final Map<FasterString, FunctionDefinition> nameToFun, final Map<FunctionDef, FunctionDefinition> funToFun, final AtomicInteger id) { if (e instanceof DoubleVal) { return new NumericConstant(((DoubleVal) e).getVal()); } if (e instanceof StringVal) { return new Constant<>(((StringVal) e).getVal()); } if (e instanceof BooleanVal) { return new Constant<>(((BooleanVal) e).isVal()); } if (e instanceof TupleVal) { final List<Expression> expr = extractArgs((TupleVal) e); List<AnnotatedTree<?>> list = new ArrayList<>(); for (Expression exp : expr) { list.add(parseExpression(exp, nameToFun, funToFun, id)); } final AnnotatedTree<?>[] args = new AnnotatedTree<?>[list.size()]; int i = 0; for (AnnotatedTree<?> t : list) { args[i++] = t; } return new CreateTuple(args); } if (e instanceof VAR) { return new Variable(((VAR) e).getName()); } if (e == null) { throw new IllegalArgumentException("null expression, this is a bug."); } final EObject eRef = e.getReference(); if (eRef != null) { /* * Function or method call */ if (eRef instanceof JvmOperation) { final JvmOperation m = (JvmOperation) eRef; return parseMethod(m, extractArgs(e), nameToFun, funToFun, id); } else if (eRef instanceof FunctionDef) { final FunctionDef fun = (FunctionDef) eRef; return parseFunction(fun, extractArgs(e), nameToFun, funToFun, id); } else { throw new IllegalStateException("I do not know how I should interpret a call to a " + eRef.getClass().getSimpleName() + " object."); } } final String name = e.getName(); if (name == null) { /* * Envelope: recurse in */ final EObject val = e.getV(); return parseExpression((Expression) val, nameToFun, funToFun, id); } if (BINARY_OPERATORS.contains(name) && e.getLeft() != null && e.getRight() != null) { return new BinaryOp(name, parseExpression(e.getLeft(), nameToFun, funToFun, id), parseExpression(e.getRight(), nameToFun, funToFun, id)); } if (UNARY_OPERATORS.contains(name) && e.getLeft() == null && e.getRight() != null) { return new UnaryOp(name, parseExpression(e.getRight(), nameToFun, funToFun, id)); } if (name.equals(REP_NAME)) { final RepInitialize ri = e.getInit().getArgs().get(0); final String x = ri.getX().getName(); return new RepCall<>(new FasterString(x), parseExpression(ri.getW(), nameToFun, funToFun, id), parseBlock(e.getBody(), nameToFun, funToFun, id)); } if (name.equals(IF_NAME)) { @SuppressWarnings(UNCHECKED) final AnnotatedTree<Boolean> cond = (AnnotatedTree<Boolean>) parseExpression(e.getCond(), nameToFun, funToFun, id); @SuppressWarnings(UNCHECKED) final AnnotatedTree<T> then = (AnnotatedTree<T>) parseBlock(e.getThen(), nameToFun, funToFun, id); @SuppressWarnings(UNCHECKED) final AnnotatedTree<T> elze = (AnnotatedTree<T>) parseBlock(e.getElse(), nameToFun, funToFun, id); return new If<>(cond, then, elze); } if (name.equals(PI_NAME)) { return new Constant<>(Math.PI); } if (name.equals(E_NAME)) { return new Constant<>(Math.E); } if (name.equals(RAND_NAME)) { return new Random(); } if (name.equals(DT_NAME)) { return new Dt(); } if (name.equals(SELF_NAME)) { return new Self(); } if (name.equals(NBR_NAME)) { return new NBRCall(parseExpression(e.getArg(), nameToFun, funToFun, id)); } if (name.equals(ALIGNED_MAP)) { @SuppressWarnings(UNCHECKED) final AnnotatedTree<Field> arg = (AnnotatedTree<Field>) parseExpression(e.getArg(), nameToFun, funToFun, id); @SuppressWarnings(UNCHECKED) final AnnotatedTree<FunctionDefinition> cond = (AnnotatedTree<FunctionDefinition>) parseExpression( e.getCond(), nameToFun, funToFun, id); @SuppressWarnings(UNCHECKED) final AnnotatedTree<FunctionDefinition> op = (AnnotatedTree<FunctionDefinition>) parseExpression( e.getOp(), nameToFun, funToFun, id); final AnnotatedTree<?> def = parseExpression(e.getDefault(), nameToFun, funToFun, id); return new AlignedMap(arg, cond, op, def); } if (name.equals(LAMBDA_NAME)) { final FunctionDefinition lambda = new FunctionDefinition("l$" + id.getAndIncrement(), extractArgsFromLambda(e)); final AnnotatedTree<?> body = parseBlock(e.getBody(), nameToFun, funToFun, id); lambda.setBody(body); return new Constant<>(lambda); } if (name.equals(NBR_RANGE)) { return new NBRRange(); } if (name.equals(EVAL_NAME)) { final AnnotatedTree<?> arg = parseExpression(e.getArg(), nameToFun, funToFun, id); return new Eval(arg); } if (name.equals(DOT_NAME)) { final AnnotatedTree<?> target = parseExpression(e.getLeft(), nameToFun, funToFun, id); final List<AnnotatedTree<?>> args = parseArgs(extractArgs(e), nameToFun, funToFun, id); return new DotOperator(e.getMethodName(), target, args); } if (name.equals(MUX_NAME)) { final AnnotatedTree<?> cond = parseExpression(e.getCond(), nameToFun, funToFun, id); final AnnotatedTree<?> then = parseBlock(e.getThen(), nameToFun, funToFun, id); final AnnotatedTree<?> elze = parseBlock(e.getElse(), nameToFun, funToFun, id); return new TernaryOp(MUX_NAME, cond, then, elze); } if (name.endsWith(HOOD_END)) { final String op = name.replace(HOOD_END, ""); final HoodOp hop = HoodOp.get(op); if (hop == null) { throw new UnsupportedOperationException("Unsupported hood operation: " + op); } @SuppressWarnings(UNCHECKED) final AnnotatedTree<Field> arg = (AnnotatedTree<Field>) parseExpression(e.getArg(), nameToFun, funToFun, id); return new HoodCall(arg, hop, e.isInclusive()); } throw new UnsupportedOperationException( "Unsupported operation: " + (e.getName() != null ? e.getName() : "Unknown")); } private static List<Expression> extractArgs(final Expression e) { return (List<Expression>) (e.getArgs() != null && e.getArgs().getArgs() != null ? e.getArgs().getArgs() : Collections.emptyList()); } private static List<Expression> extractArgs(final TupleVal e) { return (List<Expression>) (e.getArgs() != null && e.getArgs().getArgs() != null ? e.getArgs().getArgs() : Collections.emptyList()); } private static List<VAR> extractArgs(final FunctionDef e) { return (List<VAR>) (e.getArgs() != null && e.getArgs().getArgs() != null ? e.getArgs().getArgs() : Collections.emptyList()); } private static List<VAR> extractArgsFromLambda(final Expression e) { return (List<VAR>) (e.getLambdaArgs() != null && e.getLambdaArgs().getArgs() != null ? e.getLambdaArgs().getArgs() : Collections.emptyList()); } }