/* * * Copyright (C) 2010 Remco Bouckaert remco@cs.auckland.ac.nz * * This file is part of BEAST2. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership and licensing. * * BEAST is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * BEAST is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with BEAST; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ package beast.core; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public interface BEASTInterface { /** * initAndValidate is supposed to check validity of values of inputs, and initialise. * If for some reason this fails, the most appropriate exception to throw is * IllegalArgumentException (if the combination of input values is not correct) * or otherwise a RuntimeException. */ public void initAndValidate(); /** identifiable **/ public String getID(); public void setID(String ID); /** return set of Outputs, that is Objects for which this object is an Input **/ public Set<BEASTInterface> getOutputs(); /** return Map of Inputs containing both Inputs and InptForAnnotatedConstructors * indexed by Input name **/ public Map<String, Input<?>> getInputs(); /* Utility for testing purposes only. * This cannot be done in a constructor, since the * inputs will not exist yet at that point in time * and listInputs returns a list of nulls! * Assigns objects to inputs in order in which the * inputs are declared in the class, then calls * initAndValidate(). */ default public void init(final Object... objects) { final List<Input<?>> inputs = listInputs(); int i = 0; for (final Object object : objects) { inputs.get(i++).setValue(object, this); } initAndValidate(); } // init /* Utility for testing purposes * The arguments are alternating input names and values, * and values are assigned to the input with the particular name. * For example initByName("kappa", 2.0, "lambda", true) * assigns 2 to input kappa and true to input lambda. * After assigning inputs, initAndValidate() is called. */ default public void initByName(final Object... objects) { if (objects.length % 2 == 1) { throw new RuntimeException("Expected even number of arguments, name-value pairs"); } for (int i = 0; i < objects.length; i += 2) { if (objects[i] instanceof String) { final String name = (String) objects[i]; setInputValue(name, objects[i + 1]); } else { throw new RuntimeException("Expected a String in " + i + "th argument "); } } try { initAndValidate(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("initAndValidate() failed! " + e.getMessage()); } } // initByName @SuppressWarnings({"unchecked", "rawtypes" }) static Set<BEASTInterface> getOutputs(Object object) { try { Method method = object.getClass().getMethod("getOutputs"); Object outputs = method.invoke(object); if (outputs instanceof Set<?>) { return (Set) outputs; } throw new RuntimeException("call to getOutputs() on object did not return a java.util.Set"); } catch (Exception e) { throw new RuntimeException("could not call getOutputs() on object: " + e.getMessage()); } } /** * @return description from @Description annotation */ default public String getDescription() { final Annotation[] classAnnotations = this.getClass().getAnnotations(); for (final Annotation annotation : classAnnotations) { if (annotation instanceof Description) { final Description description = (Description) annotation; return description.value(); } } return "Not documented!!!"; } /** * Deprecated: use getCitationList() instead to allow multiple citations, not just the first one */ @Deprecated default public Citation getCitation() { final Annotation[] classAnnotations = this.getClass().getAnnotations(); for (final Annotation annotation : classAnnotations) { if (annotation instanceof Citation) { return (Citation) annotation; } if (annotation instanceof Citation.Citations) { return ((Citation.Citations) annotation).value()[0]; // TODO: this ignores other citations } } return null; } /** * @return array of @Citation annotations for this class * or empty list if there are no citations **/ default public List<Citation> getCitationList() { final Annotation[] classAnnotations = this.getClass().getAnnotations(); List<Citation> citations = new ArrayList<>(); for (final Annotation annotation : classAnnotations) { if (annotation instanceof Citation) { citations.add((Citation) annotation); } if (annotation instanceof Citation.Citations) { for (Citation citation : ((Citation.Citations) annotation).value()) { citations.add(citation); } } } return citations; } /** * @return references for this plug in and all its inputs * */ default public String getCitations() { return getCitations(new HashSet<>(), new HashSet<>()); } default String getCitations(final HashSet<String> citations, final HashSet<String> IDs) { if (getID() != null) { if (IDs.contains(getID())) { return ""; } IDs.add(getID()); } final StringBuilder buf = new StringBuilder(); // only add citation if it is not already processed for (Citation citation : getCitationList()) { if (!citations.contains(citation.value())) { // and there is actually a citation to add buf.append("\n"); buf.append(citation.value()); buf.append("\n"); citations.add(citation.value()); } } try { for (final BEASTInterface beastObject : listActiveBEASTObjects()) { buf.append(beastObject.getCitations(citations, IDs)); } } catch (Exception e) { e.printStackTrace(); } return buf.toString(); } // getCitations /** * create list of inputs to this plug-in * */ default public List<Input<?>> listInputs() { final List<Input<?>> inputs = new ArrayList<>(); // First, collect all Inputs final Field[] fields = getClass().getFields(); for (final Field field : fields) { if (field.getType().isAssignableFrom(Input.class)) { try { final Input<?> input = (Input<?>) field.get(this); inputs.add(input); } catch (IllegalAccessException e) { // not a publicly accessible input, ignore } } } // Second, collect InputForAnnotatedConstructors of annotated constructor (if any) Constructor<?>[] allConstructors = this.getClass().getDeclaredConstructors(); for (Constructor<?> ctor : allConstructors) { Annotation[][] annotations = ctor.getParameterAnnotations(); List<Param> paramAnnotations = new ArrayList<>(); for (Annotation [] a0 : annotations) { for (Annotation a : a0) { if (a instanceof Param) { paramAnnotations.add((Param) a); } } } Class<?>[] types = ctor.getParameterTypes(); Type[] gtypes = ctor.getGenericParameterTypes(); if (types.length > 0 && paramAnnotations.size() > 0) { int offset = 0; if (types.length == paramAnnotations.size() + 1) { offset = 1; } for (int i = 0; i < paramAnnotations.size(); i++) { Param param = paramAnnotations.get(i); Type type = types[i + offset]; Class<?> clazz = null; try { clazz = Class.forName(type.getTypeName()); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (clazz.isAssignableFrom(List.class)) { Type[] genericTypes2 = ((ParameterizedType) gtypes[i + offset]).getActualTypeArguments(); Class<?> theClass = (Class<?>) genericTypes2[0]; InputForAnnotatedConstructor<?> t = null; try { t = new InputForAnnotatedConstructor<>(this, theClass, param); } catch (NoSuchMethodException | SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } inputs.add(t); } else { InputForAnnotatedConstructor<?> t = null; try { t = new InputForAnnotatedConstructor<>(this, types[i + offset], param); } catch (NoSuchMethodException | SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } inputs.add(t); } } } } return inputs; } // listInputs /** * create array of all plug-ins in the inputs that are instantiated. * If the input is a List of plug-ins, these individual plug-ins are * added to the list. * * @return list of all active plug-ins * @throws IllegalAccessException */ default public List<BEASTInterface> listActiveBEASTObjects() { final List<BEASTInterface> beastObjects = new ArrayList<>(); for (Input<?> input : getInputs().values()) { if (input.get() != null) { if (input.get() instanceof List<?>) { final List<?> list = (List<?>) input.get(); for (final Object o : list) { if (o instanceof BEASTInterface) { beastObjects.add((BEASTInterface) o); } } } else if (input.get() != null && input.get() instanceof BEASTInterface) { beastObjects.add((BEASTInterface) input.get()); } } } return beastObjects; } @Deprecated /** use listActiveBEASTObjects instead **/ default public List<BEASTInterface> listActivePlugins() throws IllegalArgumentException, IllegalAccessException { return listActiveBEASTObjects(); } // listActivePlugins /** * get description of an input * * @param name of the input * @return description of input */ default public String getTipText(final String name) throws IllegalArgumentException, IllegalAccessException { try { Input<?> input = getInput(name); if (input != null) { return input.getTipText(); } } catch (Exception e) { // whatever happened, getting a tip text is no reason to interrupt anything, // so ignore and return null } return null; } // getTipText /** * check whether the input is an Integer, Double, Boolean or String * */ default public boolean isPrimitive(final String name) { final Input<?> input = getInput(name); final Class<?> inputType = input.getType(); if (inputType == null) { input.determineClass(this); } assert inputType != null; for (final Class<?> c : new Class[]{Integer.class, Long.class, Double.class, Float.class, Boolean.class, String.class}) { if (inputType.isAssignableFrom(c)) { return true; } } return false; } // isPrimitive /** * get value of an input by input name * */ default public Object getInputValue(final String name) { final Input<?> input = getInput(name); return input.get(); } // getInputValue /** * set value of an input by input name * */ default public void setInputValue(final String name, final Object value) { final Input<?> input = getInput(name); if (!input.canSetValue(value, this)) { throw new RuntimeException("Cannot set input value of " + name); } input.setValue(value, this); } // setInputValue /** * get input by input name * */ default public Input<?> getInput(final String name) { Map<String, Input<?>> inputs = getInputs(); if (inputs.containsKey(name)) { return inputs.get(name); } String inputNames = " "; // <- space here to prevent error in .substring below for (final Input<?> input : listInputs()) { inputNames += input.getName() + ","; } throw new IllegalArgumentException("This BEASTInterface (" + (this.getID() == null ? this.getClass().getName() : this.getID()) + ") has no input with name " + name + ". " + "Choose one of these inputs:" + inputNames.substring(0, inputNames.length() - 1)); } // getInput /** * check validation rules for all its inputs * * @throws IllegalArgumentException when validation fails */ default public void validateInputs() { for (final Input<?> input : listInputs()) { input.validate(); } } /** * Collect all predecessors in the graph where inputs * represent incoming edges and plug-ins nodes. * * @param predecessors in partial order such that if * x is after y in the list then x is not an ancestor of y * (but x need not necessarily be a predecesor of y) */ default public void getPredecessors(final List<BEASTInterface> predecessors) { predecessors.add(this); for (final BEASTInterface beastObject2 : listActiveBEASTObjects()) { if (!predecessors.contains(beastObject2)) { beastObject2.getPredecessors(predecessors); } } } }