All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graphstream.algorithm.Parameter Maven / Gradle / Ivy

Go to download

The GraphStream library. With GraphStream you deal with graphs. Static and Dynamic. You create them from scratch, from a file or any source. You display and render them. This package contains algorithms and generators.

There is a newer version: 2.0
Show newest version
/*
 * Copyright 2006 - 2015
 *     Stefan Balev     
 *     Julien Baudry    
 *     Antoine Dutot    
 *     Yoann Pigné      
 *     Guilhelm Savin   
 * 
 * This file is part of GraphStream .
 * 
 * GraphStream is a library whose purpose is to handle static or dynamic
 * graph, create them from scratch, file or any source and display them.
 * 
 * This program is free software distributed under the terms of two licenses, the
 * CeCILL-C license that fits European law, and the GNU Lesser General Public
 * License. You can  use, modify and/ or redistribute the software under the terms
 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
 * URL  or under the terms of the GNU LGPL as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * This program 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 this program.  If not, see .
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
 */
package org.graphstream.algorithm;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.InvalidPropertiesFormatException;
import java.util.LinkedList;
import java.util.Properties;

import org.graphstream.algorithm.DefineParameter;
import org.graphstream.algorithm.InvalidParameterException;
import org.graphstream.algorithm.MissingParameterException;

/**
 * Defines a parameter as an association between a String and an Object.
 */
public class Parameter {
	/**
	 * Shortcut for "new Parameter(key,value)".
	 * 
	 * @param key
	 *            key of the parameter
	 * @param value
	 *            value of the parameter
	 * @return new Parameter(key,value)
	 */
	public static final Parameter parameter(String key, Object value) {
		return new Parameter(key, value);
	}

	/**
	 * Process parameters. Throws an exception if something is wrong.
	 * 
	 * @param env
	 *            object to set attributes
	 * @param params
	 *            parameters
	 * @throws InvalidParameterException
	 */
	public static void processParameters(Object env, Parameter... params)
			throws InvalidParameterException, MissingParameterException {
		//
		// We are lazy, if no parameters there is no need to work !
		//
		if (params == null || params.length == 0)
			return;

		//
		// Create a new ParametersProcessor to process the parameters and
		// run it.
		//
		ParametersProcessor pp = new ParametersProcessor(env, params);
		pp.process();
	}

	public static void processParameters(Object env, Properties prop)
			throws InvalidParameterException, MissingParameterException {
		//
		// Create a new ParametersProcessor to process the parameters and
		// run it.
		//
		ParametersProcessor pp = new ParametersProcessor(env, prop);
		pp.process();
	}

	public static void processParameters(Object env, String propertiesPath)
			throws InvalidParameterException, MissingParameterException, InvalidPropertiesFormatException, IOException {
		boolean xml = propertiesPath.endsWith(".xml");
		FileInputStream in = new FileInputStream(propertiesPath);
		
		processParameters(env, in, xml);
		
		in.close();
	}
	public static void processParameters(Object env, InputStream in, boolean xml)
	throws InvalidParameterException, MissingParameterException, InvalidPropertiesFormatException, IOException {
		Properties prop = new Properties();
		
		if (xml)
			prop.loadFromXML(in);
		else
			prop.load(in);
		
		processParameters(env, prop);
	}

	public static Properties exportParameters(Object env) {
		ParametersProcessor pp = new ParametersProcessor(env);
		Properties prop = new Properties();
		
		for (Field f : pp.fields.keySet()) {
			DefineParameter dp = f.getAnnotation(DefineParameter.class);
			
			try {
				f.setAccessible(true);
			} catch (Exception e) {
				// Can't change permission...
				// Trying anyway to continue !
			}
			
			try {
				prop.setProperty(dp.name(), f.get(env).toString());
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		
		return prop;
	}

	/**
	 * Defines the object which will process parameters.
	 * 
	 */
	public static class ParametersProcessor {
		HashMap definedParameters;
		HashMap fields;
		HashMap params;

		public ParametersProcessor(Object obj) {
			init(obj);
		}
		
		public ParametersProcessor(Object obj, Properties prop) {
			init(obj);
			buildParametersMap(prop);
		}

		public ParametersProcessor(Object obj, Parameter... parameters) {
			init(obj);

			if (parameters != null)
				buildParametersMap(parameters);
		}

		private void init(Object obj) {
			fields = new HashMap();
			params = new HashMap();
			definedParameters = new HashMap();

			if (obj.getClass().isArray()) {
				Object[] objects = (Object[]) obj;

				for (Object o : objects)
					buildFields(o);
			} else {
				buildFields(obj);
			}
		}

		/**
		 * Start the processing part. First, this checks is all non-optional
		 * parameters will receive a value, else a MissingParameterException is
		 * thrown. Then, values are assigned to the attribute (
		 * {@link ParametersProcessor#setValue(DefineParameter, Field, Object)}
		 * ). Finally, the method checks is no parameter remaining. In this
		 * case, an InvalidParameterException is thrown.
		 * 
		 * @throws InvalidParameterException
		 * @throws MissingParameterException
		 */
		public void process() throws InvalidParameterException,
				MissingParameterException {
			checkNonOptionalParameters();

			LinkedList remainingParameters = new LinkedList(
					params.keySet());

			for (Field f : fields.keySet()) {
				final DefineParameter dp = f
						.getAnnotation(DefineParameter.class);

				if (params.containsKey(dp.name())) {
					//
					// We try to make the field accessible,
					// if it is a protected or private field.
					//
					try {
						f.setAccessible(true);
					} catch (Exception e) {
						// Can't change permission...
						// Trying anyway to continue !
					}

					final Object value = params.get(dp.name());

					setValue(dp, f, value);

					remainingParameters.remove(dp.name());
				}
			}

			//
			// If values remain in paramsMap, user try to define parameters
			// which do not exist.
			//
			if (remainingParameters.size() > 0) {
				String uneatenParams = "";

				for (String s : remainingParameters)
					uneatenParams += String.format("%s\"%s\"", (uneatenParams
							.length() > 0 ? ", " : ""), s);

				throw new InvalidParameterException(
						"some parameters does not exist : %s", uneatenParams);
			}
		}

		/**
		 * Find fields owning a DefineParameter annotation.
		 * 
		 * @param obj
		 *            the object on which searching fields.
		 */
		protected void buildFields(Object obj) {
			Class cls = obj.getClass();

			while (cls != Object.class) {
				Field[] clsFields = cls.getDeclaredFields();

				if (clsFields != null) {
					for (Field f : clsFields) {
						DefineParameter dp = f
								.getAnnotation(DefineParameter.class);

						if (dp != null) {
							fields.put(f, obj);
							definedParameters.put(dp.name(), f);
						}
					}
				}

				cls = cls.getSuperclass();
			}
		}

		/**
		 * Create a map "key -> value" for parameters.
		 * 
		 * @param parameters
		 *            parameters which will be used in the process part.
		 */
		protected void buildParametersMap(Parameter... parameters) {
			for (Parameter p : parameters)
				params.put(p.getKey(), p.getValue());
		}

		protected void buildParametersMap(Properties prop) {
			for (String key : prop.stringPropertyNames()) {
				Field f = definedParameters.get(key);
				String v = prop.getProperty(key);

				Class type = f.getType();

				if (type.equals(String.class))
					params.put(key, v);
				else if (type.equals(Double.class) || type.equals(Double.TYPE))
					params.put(key, Double.valueOf(v));
				else if (type.equals(Float.class) || type.equals(Float.TYPE))
					params.put(key, Float.valueOf(v));
				else if (type.equals(Integer.class) || type.equals(Integer.TYPE))
					params.put(key, Integer.valueOf(v));
				else if (type.equals(Long.class) || type.equals(Long.TYPE))
					params.put(key, Long.valueOf(v));
				else if (type.equals(Boolean.class) || type.equals(Boolean.TYPE))
					params.put(key, Boolean.valueOf(v));
				else if (type.isEnum()) {
					@SuppressWarnings("unchecked")
					Class> e = (Class>) type;
					
					for (Enum en : e.getEnumConstants()) {
						if (en.name().equals(v)) {
							params.put(key, en);
							break;
						}
					}
				} else
					throw new UnsupportedOperationException(type.getName());
			}
		}

		/**
		 * Set the value of a field according to a parameter. First, the value
		 * is check ( {@link #checkValue(DefineParameter, Field, Object)} ).
		 * Then, the beforeSet trigger is called (
		 * {@link #callBeforeSetTrigger(DefineParameter, Field, Object)} ).
		 * Then, if 'setters' value is empty, value is simply assigned to the
		 * field, else 'setter' is called (
		 * {@link #callSetter(DefineParameter, Field, Object)} ). Finally, the
		 * afterSet trigger is called (
		 * {@link #callAfterSetTrigger(DefineParameter, Field, Object)}).
		 * 
		 * @param dp
		 *            the DefineParameter annotation being set.
		 * @param f
		 *            field associated to the annotation.
		 * @param value
		 *            value which will be assigned to the field.
		 * @throws InvalidParameterException
		 */
		protected void setValue(DefineParameter dp, Field f, Object value)
				throws InvalidParameterException {
			checkValue(dp, f, value);

			Object env = fields.get(f);

			callBeforeSetTrigger(dp, f, value);

			if (dp.setter().length() == 0) {
				try {
					f.set(env, value);
				} catch (IllegalArgumentException e) {
					throw new InvalidParameterException(
							"invalid value type for %s, %s expected",
							dp.name(), f.getType().getName());
				} catch (IllegalAccessException e) {
					throw new InvalidParameterException(
							"parameter value can not be set. maybe a permission problem");
				}
			} else {
				callSetter(dp, f, value);
			}

			callAfterSetTrigger(dp, f, value);
		}

		/**
		 * Check is the value is valid according to the DefineParameter
		 * annotation.
		 * 
		 * @param dp
		 *            the DefineParameter annotation associated to the field.
		 * @param f
		 *            the field.
		 * @param value
		 *            the value.
		 * @throws InvalidParameterException
		 */
		protected void checkValue(DefineParameter dp, Field f, Object value)
				throws InvalidParameterException {
			final boolean isNumber = value instanceof Number;

			//
			// If type is defined, value should be assignable to this
			// type.
			//
			if (dp.type() != Object.class
					&& !dp.type().isAssignableFrom(value.getClass()))
				throw new InvalidParameterException(
						"invalid parameter type, should be %s", dp.type()
								.getName());

			//
			// If min or max are defined, value should be a number
			// between min and max.
			//
			if (!Double.isNaN(dp.min()) || !Double.isNaN(dp.max())) {
				if (!isNumber)
					throw new InvalidParameterException(
							"min or max defined but value is not a number for %s",
							dp.name());

				Number n = (Number) value;

				if (dp.min() != Double.NaN && n.doubleValue() < dp.min())
					throw new InvalidParameterException(String.format(
							"bad value for \"%s\", %f < min", dp.name(), n
									.doubleValue()));

				if (dp.max() != Double.NaN && n.doubleValue() > dp.max())
					throw new InvalidParameterException(String.format(
							"bad value for \"%s\", %f > max", dp.name(), n
									.doubleValue()));
			}

			//
			// If strings is defined, value should be a String and must
			// be one of the defined values.
			//
			if (dp.strings().length > 0) {
				if (value.getClass() != String.class)
					throw new InvalidParameterException(
							"value should be a String");

				String s = (String) value;
				boolean found = false;

				for (String alt : dp.strings())
					if (alt.equals(s)) {
						found = true;
						break;
					}

				if (!found)
					throw new InvalidParameterException(
							"\"%s\" is not in the allowed values for %s",
							value, dp.name());
			}
		}

		/**
		 * Check if all non-optional parameters will receive a value.
		 * 
		 * @throws MissingParameterException
		 *             a non-optional parameter does not receive its value.
		 */
		protected void checkNonOptionalParameters()
				throws MissingParameterException {
			for (Field f : fields.keySet()) {
				DefineParameter dp = f.getAnnotation(DefineParameter.class);

				if (!dp.optional() && !params.containsKey(dp.name()))
					throw new MissingParameterException(
							"parameter \"%s\" is missing", dp.name());
			}
		}

		/**
		 * Call setter of a parameter. This is called when
		 * {@link DefineParameter#setter()} is not empty. If arguments count of
		 * the setter is 1, then the value is passed as argument. If count is 2,
		 * then parameter name and value are passed as arguments. Else, a
		 * InvalidParameterException is thrown.
		 * 
		 * @param dp
		 *            the DefineParameter annotation associated to the field.
		 * @param f
		 *            the field.
		 * @param value
		 *            the value.
		 * @throws InvalidParameterException
		 */
		protected void callSetter(DefineParameter dp, Field f, Object value)
				throws InvalidParameterException {
			Object env = fields.get(f);

			Method setter = null;

			{
				Class cls = env.getClass();

				while (setter == null && cls != Object.class) {
					Method[] methods = cls.getDeclaredMethods();

					if (methods != null) {
						for (Method m : methods)
							if (m.getName().equals(dp.setter())) {
								setter = m;
								break;
							}
					}

					cls = cls.getSuperclass();
				}
			}

			if (setter == null)
				throw new InvalidParameterException(
						"'setter' '%s()' can not be found for %s", dp.setter(),
						dp.name());

			Object[] args = null;

			switch (setter.getParameterTypes().length) {
			case 1:
				// If trigger has one argument, we pass the value of
				// the parameter.
				args = new Object[] { value };
				break;
			case 2:
				// If trigger has two arguments, we pass the key and
				// the value of the parameter.
				args = new Object[] { dp.name(), value };
				break;
			default:
				throw new InvalidParameterException(
						"bad argument count in 'setter' '%s()' for %s", dp
								.setter(), dp.name());
			}

			try {
				setter.invoke(env, args);
			} catch (IllegalArgumentException e) {
				throw new InvalidParameterException(
						"bad arguments in 'setter' '%s()'for %s", dp.setter(),
						dp.name());
			} catch (IllegalAccessException e) {
				throw new InvalidParameterException(
						"illegal access to 'setter' '%s()' for %s",
						dp.setter(), dp.name());
			} catch (InvocationTargetException e) {
				throw new InvalidParameterException(
						"invocation error of 'setter' '%s()' for %s", dp
								.setter(), dp.name());
			}
		}

		/**
		 * Call the beforeSet trigger. The name of the method to call can be
		 * defined in @link {@link DefineParameter#beforeSet()}. If arguments
		 * count of the method is 0, no argument is given. If 1, then the value
		 * is given. If 2, then the parameter name and its value are given.
		 * Else, throw an InvalidParameterException.
		 * 
		 * @param dp
		 *            the DefineParameter annotation associated to the field.
		 * @param f
		 *            the field.
		 * @param value
		 *            the value.
		 * @throws InvalidParameterException
		 */
		protected void callBeforeSetTrigger(DefineParameter dp, Field f,
				Object value) throws InvalidParameterException {
			if (dp.beforeSet().length() > 0) {
				Object env = fields.get(f);

				Method beforeSet = null;

				{
					Class cls = env.getClass();

					while (beforeSet == null && cls != Object.class) {
						Method[] methods = cls.getDeclaredMethods();

						if (methods != null) {
							for (Method m : methods)
								if (m.getName().equals(dp.beforeSet())) {
									beforeSet = m;
									break;
								}
						}

						cls = cls.getSuperclass();
					}
				}

				if (beforeSet == null)
					throw new InvalidParameterException(
							"'beforeSet' trigger '%s()' can not be found for %s",
							dp.beforeSet(), dp.name());

				Object[] args = null;

				switch (beforeSet.getParameterTypes().length) {
				case 0:
					// Nothing
					break;
				case 1:
					// If trigger has one argument, we pass the value of
					// the parameter.
					args = new Object[] { value };
					break;
				case 2:
					// If trigger has two arguments, we pass the key and
					// the value of the parameter.
					args = new Object[] { dp.name(), value };
					break;
				default:
					throw new InvalidParameterException(
							"two much arguments in 'beforeSet' trigger '%s()' for %s",
							dp.beforeSet(), dp.name());
				}

				try {
					beforeSet.invoke(env, args);
				} catch (IllegalArgumentException e) {
					throw new InvalidParameterException(
							"bad arguments in 'beforeSet' trigger '%s()'for %s",
							dp.beforeSet(), dp.name());
				} catch (IllegalAccessException e) {
					throw new InvalidParameterException(
							"illegal access to 'beforeSet' trigger '%s()' for %s",
							dp.beforeSet(), dp.name());
				} catch (InvocationTargetException e) {
					throw new InvalidParameterException(
							"invocation error of 'beforeSet' trigger '%s()' for %s",
							dp.beforeSet(), dp.name());
				}
			}
		}

		/**
		 * Call the afterSet trigger. The name of the method to call can be
		 * defined in @link {@link DefineParameter#afterSet()}. If arguments
		 * count of the method is 0, no argument is given. If 1, then the value
		 * is given. If 2, then the parameter name and its value are given.
		 * Else, throw an InvalidParameterException.
		 * 
		 * @param dp
		 *            the DefineParameter annotation associated to the field.
		 * @param f
		 *            the field.
		 * @param value
		 *            the value.
		 * @throws InvalidParameterException
		 */
		protected void callAfterSetTrigger(DefineParameter dp, Field f,
				Object value) throws InvalidParameterException {
			Object env = fields.get(f);

			if (dp.afterSet().length() > 0) {
				Method afterSet = null;

				{
					Class cls = env.getClass();

					while (afterSet == null && cls != Object.class) {
						Method[] methods = cls.getDeclaredMethods();

						if (methods != null) {
							for (Method m : methods)
								if (m.getName().equals(dp.afterSet())) {
									afterSet = m;
									break;
								}
						}

						cls = cls.getSuperclass();
					}
				}

				if (afterSet == null)
					throw new InvalidParameterException(
							"'afterSet' trigger '%s()' can not be found for %s",
							dp.afterSet(), dp.name());

				Object[] args = null;

				switch (afterSet.getParameterTypes().length) {
				case 0:
					// Nothing
					break;
				case 1:
					// If trigger has one argument, we pass the value of
					// the parameter.
					args = new Object[] { value };
					break;
				case 2:
					// If trigger has two arguments, we pass the key and
					// the value of the parameter.
					args = new Object[] { dp.name(), value };
					break;
				default:
					throw new InvalidParameterException(
							"two much arguments in 'afterSet' trigger '%s()' for %s",
							dp.afterSet(), dp.name());
				}

				try {
					afterSet.invoke(env, args);
				} catch (IllegalArgumentException e) {
					throw new InvalidParameterException(
							"bad arguments in 'afterSet' trigger '%s()'for %s",
							dp.afterSet(), dp.name());
				} catch (IllegalAccessException e) {
					throw new InvalidParameterException(
							"illegal access to 'afterSet' trigger '%s()' for %s",
							dp.afterSet(), dp.name());
				} catch (InvocationTargetException e) {
					throw new InvalidParameterException(
							"invocation error of 'afterSet' trigger '%s()' for %s",
							dp.afterSet(), dp.name());
				}
			}
		}
	}

	/**
	 * Key of the parameter.
	 */
	protected String key;
	/**
	 * Value of the parameter.
	 */
	protected Object value;

	/**
	 * Build a new parameter.
	 * 
	 * @param key
	 * @param value
	 */
	public Parameter(String key, Object value) {
		this.key = key;
		this.value = value;
	}

	/**
	 * Get the key of this parameter.
	 * 
	 * @return the key
	 */
	public String getKey() {
		return key;
	}

	/**
	 * Get the value of this parameter.
	 * 
	 * @param 
	 *            type asked for the value
	 * @return the value as a T
	 */
	@SuppressWarnings("unchecked")
	public  T getValue() {
		return (T) value;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy