/* * JaamSim Discrete Event Simulation * Copyright (C) 2016 JaamSim Software 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.jaamsim.input; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.jaamsim.datatypes.DoubleVector; import com.jaamsim.datatypes.IntegerVector; import com.jaamsim.input.ExpResult.Iterator; import com.jaamsim.units.DimensionlessUnit; import com.jaamsim.units.Unit; public class ExpCollections { public static boolean isCollectionClass(Class<?> klass) { if (Map.class.isAssignableFrom(klass)) { return true; } if (List.class.isAssignableFrom(klass)) { return true; } if (DoubleVector.class.isAssignableFrom(klass)) { return true; } if (IntegerVector.class.isAssignableFrom(klass)) { return true; } if (klass.isArray()) { return true; } return false; } public static ExpResult getCollection(Object obj, Class<? extends Unit> ut) { if (obj instanceof Map) { MapCollection col = new MapCollection((Map<?,?>)obj, ut); return ExpResult.makeCollectionResult(col); } if (obj instanceof List) { ListCollection col = new ListCollection((List<?>)obj, ut); return ExpResult.makeCollectionResult(col); } if (obj.getClass().isArray()) { ArrayCollection col = new ArrayCollection(obj, ut); return ExpResult.makeCollectionResult(col); } if (obj instanceof DoubleVector) { DoubleVectorCollection col = new DoubleVectorCollection((DoubleVector)obj, ut); return ExpResult.makeCollectionResult(col); } if (obj instanceof IntegerVector) { IntegerVectorCollection col = new IntegerVectorCollection((IntegerVector)obj, ut); return ExpResult.makeCollectionResult(col); } assert false; return null; } public static ExpResult makeExpressionCollection(ArrayList<ExpResult> vals) { return ExpResult.makeCollectionResult(new AssignableArrayCollection(vals)); } private static class ListCollection implements ExpResult.Collection { private static class Iter implements ExpResult.Iterator { private int next = 0; private final List<?> list; public Iter(List<?> l) { this.list = l; } @Override public boolean hasNext() { return next < list.size(); } @Override public ExpResult nextKey() throws ExpError { ExpResult ret = ExpResult.makeNumResult(next + 1, DimensionlessUnit.class); next++; return ret; } } @Override public Iterator getIter() { return new Iter(list); } private final List<?> list; private final Class<? extends Unit> unitType; public ListCollection(List<?> l, Class<? extends Unit> ut) { this.list = l; this.unitType = ut; } @Override public ExpResult index(ExpResult index) throws ExpError { if (index.type != ExpResType.NUMBER) { throw new ExpError(null, 0, "ArrayList is not being indexed by a number"); } int indexVal = (int)index.value - 1; // Expressions use 1-base arrays if (indexVal >= list.size() || indexVal < 0) { return ExpResult.makeNumResult(0, unitType); // TODO: Is this how we want to handle this case? } Object val = list.get(indexVal); return ExpEvaluator.getResultFromObject(val, unitType); } @Override public int getSize() { return list.size(); } @Override public void assign(ExpResult key, ExpResult value) throws ExpError { throw new ExpError(null, 0, "Can not assign to built in collection"); } @Override public String getOutputString() { try { StringBuilder sb = new StringBuilder(); sb.append("{ "); for (int i = 0; i < list.size(); ++i) { ExpResult val = index(ExpResult.makeNumResult(i+1, DimensionlessUnit.class)); sb.append(val.getOutputString()); if (i < list.size() -1) { sb.append(", "); } } sb.append("}"); return sb.toString(); } catch (ExpError err) { return String.format("An error occurred: %s", err.getMessage()); } } @Override public ExpResult.Collection getCopy() { return this; } } private static class ArrayCollection implements ExpResult.Collection { private final Object array; private final Class<? extends Unit> unitType; public ArrayCollection(Object a, Class<? extends Unit> ut) { this.array = a; this.unitType = ut; } private static class Iter implements ExpResult.Iterator { private int next = 0; private final Object array; public Iter(Object a) { array = a; } @Override public boolean hasNext() { return next < Array.getLength(array); } @Override public ExpResult nextKey() throws ExpError { ExpResult ret = ExpResult.makeNumResult(next + 1, DimensionlessUnit.class); next++; return ret; } } @Override public Iterator getIter() { return new Iter(array); } @Override public ExpResult index(ExpResult index) throws ExpError { if (index.type != ExpResType.NUMBER) { throw new ExpError(null, 0, "ArrayList is not being indexed by a number"); } int indexVal = (int)index.value - 1; int length = Array.getLength(array); if (indexVal >= length || indexVal < 0) { return ExpResult.makeNumResult(0, unitType); // TODO: Is this how we want to handle this case? } Class<?> componentClass = array.getClass().getComponentType(); if (!componentClass.isPrimitive()) { // This is an object type so we can use the rest of the reflection system as usual Object val = Array.get(array, indexVal); return ExpEvaluator.getResultFromObject(val, unitType); } if ( componentClass == Double.TYPE || componentClass == Float.TYPE || componentClass == Long.TYPE || componentClass == Integer.TYPE || componentClass == Short.TYPE || componentClass == Byte.TYPE || componentClass == Character.TYPE) { // This is a numeric type and should be convertible to double return ExpResult.makeNumResult(Array.getDouble(array, indexVal), unitType); } if (componentClass == Boolean.TYPE) { // Convert boolean to 1 or 0 double val = (Array.getBoolean(array, indexVal)) ? 1.0 : 0.0; return ExpResult.makeNumResult(val, unitType); } throw new ExpError(null, 0, "Unknown type in array"); } @Override public int getSize() { return Array.getLength(array); } @Override public void assign(ExpResult key, ExpResult value) throws ExpError { throw new ExpError(null, 0, "Can not assign to built in collection"); } @Override public String getOutputString() { try { StringBuilder sb = new StringBuilder(); sb.append("{ "); for (int i = 0; i < Array.getLength(array); ++i) { ExpResult val = index(ExpResult.makeNumResult(i+1, DimensionlessUnit.class)); sb.append(val.getOutputString()); if (i < Array.getLength(array) -1) { sb.append(", "); } } sb.append("}"); return sb.toString(); } catch (ExpError err) { return String.format("An error occurred: %s", err.getMessage()); } } @Override public ExpResult.Collection getCopy() { return this; } } private static class DoubleVectorCollection implements ExpResult.Collection { private final DoubleVector vector; private final Class<? extends Unit> unitType; public DoubleVectorCollection(DoubleVector v, Class<? extends Unit> ut) { this.vector = v; this.unitType = ut; } private static class Iter implements ExpResult.Iterator { private int next = 0; private final DoubleVector vector; public Iter(DoubleVector v) { this.vector = v; } @Override public boolean hasNext() { return next < vector.size(); } @Override public ExpResult nextKey() throws ExpError { ExpResult ret = ExpResult.makeNumResult(next + 1, DimensionlessUnit.class); next++; return ret; } } @Override public Iterator getIter() { return new Iter(vector); } @Override public ExpResult index(ExpResult index) throws ExpError { if (index.type != ExpResType.NUMBER) { throw new ExpError(null, 0, "DoubleVector is not being indexed by a number"); } int indexVal = (int)index.value - 1; // Expressions use 1-base arrays if (indexVal >= vector.size() || indexVal < 0) { return ExpResult.makeNumResult(0, unitType); // TODO: Is this how we want to handle this case? } Double value = vector.get(indexVal); return ExpResult.makeNumResult(value, unitType); } @Override public int getSize() { return vector.size(); } @Override public void assign(ExpResult key, ExpResult value) throws ExpError { throw new ExpError(null, 0, "Can not assign to built in collection"); } @Override public String getOutputString() { StringBuilder sb = new StringBuilder(); sb.append("{ "); for (int i = 0; i < vector.size(); ++i) { sb.append(vector.get(i+1)*Unit.getDisplayedUnitFactor(unitType)); sb.append(" "); sb.append(Unit.getDisplayedUnit(unitType)); if (i < vector.size()) { sb.append(", "); } } sb.append("}"); return sb.toString(); } @Override public ExpResult.Collection getCopy() { return this; } } private static class IntegerVectorCollection implements ExpResult.Collection { private final IntegerVector vector; private final Class<? extends Unit> unitType; public IntegerVectorCollection(IntegerVector v, Class<? extends Unit> ut) { this.vector = v; this.unitType = ut; } private static class Iter implements ExpResult.Iterator { private int next = 0; private final IntegerVector vector; public Iter(IntegerVector v) { this.vector = v; } @Override public boolean hasNext() { return next < vector.size(); } @Override public ExpResult nextKey() throws ExpError { ExpResult ret = ExpResult.makeNumResult(next + 1, DimensionlessUnit.class); next++; return ret; } } @Override public Iterator getIter() { return new Iter(vector); } @Override public ExpResult index(ExpResult index) throws ExpError { if (index.type != ExpResType.NUMBER) { throw new ExpError(null, 0, "IntegerVector is not being indexed by a number"); } int indexVal = (int)index.value - 1; // Expressions use 1-base arrays if (indexVal >= vector.size() || indexVal < 0) { return ExpResult.makeNumResult(0, unitType); // TODO: Is this how we want to handle this case? } Integer value = vector.get(indexVal); return ExpResult.makeNumResult(value, unitType); } @Override public int getSize() { return vector.size(); } @Override public void assign(ExpResult key, ExpResult value) throws ExpError { throw new ExpError(null, 0, "Can not assign to built in collection"); } @Override public String getOutputString() { StringBuilder sb = new StringBuilder(); sb.append("{ "); for (int i = 0; i < vector.size(); ++i) { sb.append(vector.get(i+1)*Unit.getDisplayedUnitFactor(unitType)); sb.append(" "); sb.append(Unit.getDisplayedUnit(unitType)); if (i < vector.size()) { sb.append(", "); } } sb.append("}"); return sb.toString(); } @Override public ExpResult.Collection getCopy() { return this; } } private static class MapCollection implements ExpResult.Collection { private final Map<?,?> map; private final Class<? extends Unit> unitType; public MapCollection(Map<?,?> m, Class<? extends Unit> ut) { this.map = m; this.unitType = ut; } private static class Iter implements ExpResult.Iterator { java.util.Iterator<?> keySetIt; public Iter(Map<?,?> map) { keySetIt = map.keySet().iterator(); } @Override public boolean hasNext() { return keySetIt.hasNext(); } @Override public ExpResult nextKey() throws ExpError { Object mapKey = keySetIt.next(); return ExpEvaluator.getResultFromObject(mapKey, DimensionlessUnit.class); } } @Override public Iterator getIter() { return new Iter(map); } @Override public ExpResult index(ExpResult index) throws ExpError { Object key; switch (index.type) { case ENTITY: if (index.entVal == null) { throw new ExpError(null, 0, "Trying use a null entity as a key"); } key = index.entVal; break; case NUMBER: key = Double.valueOf(index.value); break; case STRING: key = index.stringVal; break; case COLLECTION: throw new ExpError(null, 0, "Can not index with a collection"); default: assert(false); key = null; break; } Object val = map.get(key); if (val == null) { return ExpResult.makeNumResult(0, unitType); // TODO: Is this how we want to handle this case? } return ExpEvaluator.getResultFromObject(val, unitType); } @Override public int getSize() { return map.size(); } @Override public void assign(ExpResult key, ExpResult value) throws ExpError { throw new ExpError(null, 0, "Can not assign to built in collection"); } @Override public String getOutputString() { try { StringBuilder sb = new StringBuilder(); sb.append("{ "); Iterator it = getIter(); while(it.hasNext()) { ExpResult index = it.nextKey(); sb.append(index.getOutputString()); sb.append(" = "); sb.append(index(index).getOutputString()); if (it.hasNext()) { sb.append(", "); } } sb.append("}"); return sb.toString(); } catch (ExpError err) { return String.format("An error occurred: %s", err.getMessage()); } } @Override public ExpResult.Collection getCopy() { return this; } } private static class AssignableArrayCollection implements ExpResult.Collection { private final ArrayList<ExpResult> list; public AssignableArrayCollection(ArrayList<ExpResult> vals) { list = new ArrayList<>(vals); } @Override public ExpResult index(ExpResult index) throws ExpError { if (index.type != ExpResType.NUMBER) { throw new ExpError(null, 0, "ArrayList is not being indexed by a number"); } int indexVal = (int)index.value - 1; // Expressions use 1-base arrays if (indexVal >= list.size() || indexVal < 0) { return ExpResult.makeNumResult(0, DimensionlessUnit.class); // TODO: Is this how we want to handle this case? } return list.get(indexVal); } @Override public void assign(ExpResult index, ExpResult value) throws ExpError { if (index.type != ExpResType.NUMBER) { throw new ExpError(null, 0, "Assignment is not being indexed by a number"); } int indexVal = (int)index.value - 1; // Expressions use 1-base arrays if (indexVal < 0) { throw new ExpError(null, 0, "Attempting to assign to a negative number: %d", indexVal); } if (indexVal >= list.size()) { // This is a dynamically expanding list, so fill in until we get to the index ExpResult filler = ExpResult.makeNumResult(0, DimensionlessUnit.class); list.ensureCapacity(indexVal+1); for (int i = list.size(); i <= indexVal; ++i) { list.add(filler); } } list.set(indexVal, value); } private static class Iter implements ExpResult.Iterator { private int next = 0; private final List<?> list; public Iter(List<?> l) { this.list = l; } @Override public boolean hasNext() { return next < list.size(); } @Override public ExpResult nextKey() throws ExpError { ExpResult ret = ExpResult.makeNumResult(next + 1, DimensionlessUnit.class); next++; return ret; } } @Override public Iterator getIter() { return new Iter(list); } @Override public int getSize() { return list.size(); } @Override public String getOutputString() { try { StringBuilder sb = new StringBuilder(); sb.append("{ "); for (int i = 0; i < list.size(); ++i) { ExpResult val = index(ExpResult.makeNumResult(i+1, DimensionlessUnit.class)); sb.append(val.getOutputString()); if (i < list.size() -1) { sb.append(", "); } } sb.append("}"); return sb.toString(); } catch (ExpError err) { return String.format("An error occurred: %s", err.getMessage()); } } @Override public ExpResult.Collection getCopy() { return new AssignableArrayCollection(list); } } }