Java tutorial
/* * This file is part of CRISIS, an economics simulator. * * Copyright (C) 2015 John Kieran Phillips * * CRISIS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CRISIS 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CRISIS. If not, see <http://www.gnu.org/licenses/>. */ package eu.crisis_economics.configuration; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Scanner; import org.apache.commons.math3.exception.NullArgumentException; final public class FromFileConfigurationContext extends ConfigurationContext { private boolean awaitingUserCreationRequest; private Hashtable<String, NameDeclarationExpression> knownNameDeclarations; // All declarations private Hashtable<String, DefinitionExpression> knownDefinitions; // All definitions private Hashtable<String, IntegerPrimitiveExpression> knownIntegerPrimitiveDefinitions; private Hashtable<String, DoublePrimitiveExpression> knownDoublePrimitiveDefinitions; private Hashtable<String, IntegerIndexOfMaximum> arrayCreationCounters; private List<CompoundExpression> compoundConfigurationExpressions; private LinkedList<UserObjectCreationTask> userObjectCreationPending; private ArrayList<DefinitionExpression> definitionExpressionRegistrationOrder; private boolean configFileParseIsComplete; public FromFileConfigurationContext(String configurationFilename) { if (configurationFilename == null) throw new NullArgumentException(); String fileContents = null; try { fileContents = new Scanner(new File(configurationFilename)).useDelimiter("\\Z").next(); } catch (IOException e) { System.out.println("FromFileConfigurationContext: file not found: '" + configurationFilename + "'"); } this.configFileParseIsComplete = false; this.knownNameDeclarations = new Hashtable<String, NameDeclarationExpression>(); this.knownDefinitions = new Hashtable<String, DefinitionExpression>(); this.knownIntegerPrimitiveDefinitions = new Hashtable<String, IntegerPrimitiveExpression>(); this.knownDoublePrimitiveDefinitions = new Hashtable<String, DoublePrimitiveExpression>(); this.userObjectCreationPending = new LinkedList<UserObjectCreationTask>(); this.definitionExpressionRegistrationOrder = new ArrayList<DefinitionExpression>(); this.arrayCreationCounters = new Hashtable<String, IntegerIndexOfMaximum>(); this.compoundConfigurationExpressions = ConfParser.parse(fileContents, this); // Populates the above hashtables this.awaitingUserCreationRequest = false; this.configFileParseIsComplete = true; } private static class UserObjectCreationTask { String uniqueName; DefinitionExpression definitionExpression; } private static class IntegerIndexOfMaximum { private Integer currentIndex, maximumIndex; IntegerIndexOfMaximum(int maximumIndex) { if (maximumIndex < 0) throw new IllegalArgumentException( "IntegerIndexOfMaximum: maximum value (" + maximumIndex + ") is non-positive."); this.currentIndex = 0; this.maximumIndex = maximumIndex; } void increment() { ++currentIndex; } boolean isComplete() { return currentIndex == maximumIndex; } } @Override public boolean isDeclared(String literalName) { if (literalName == null) throw new NullArgumentException(); return knownNameDeclarations.containsKey(literalName); } @Override public boolean hasDefinedObject(String literalName) { if (literalName == null) throw new NullArgumentException(); if (!knownDefinitions.containsKey(literalName)) return false; return (knownDefinitions.get(literalName).isPendingUserObjectCreation()); } public boolean hasDefinitionExpression(String literalName) { if (literalName == null) throw new NullArgumentException(); if (!knownDefinitions.containsKey(literalName)) return false; return true; } @Override public Object[] tryGetDefinition(String literalName) { return this.tryGetDefinitionExpression(literalName).getObject(); } public DefinitionExpression tryGetDefinitionExpression(String literalName) { if (!hasDefinitionExpression(literalName)) throw new NoSuchElementException("'" + literalName + "' has not been defined."); return knownDefinitions.get(literalName); } @Override public Integer hasPrimitiveIntegerValue(String literalName) { return this.hasPrimitiveIntegerValueExpression(literalName).getValue(); } IntegerPrimitiveExpression hasPrimitiveIntegerValueExpression(String literalName) { if (!knownIntegerPrimitiveDefinitions.containsKey(literalName)) throw new NoSuchElementException( "'" + literalName + "' does not have a primitive" + "integer definition."); return knownIntegerPrimitiveDefinitions.get(literalName); } @Override public Double hasPrimitiveDoubleValue(String literalName) { return this.hasPrimitiveDoubleValueExpression(literalName).getValue(); } /** Flatten a primitive numerical expression by replacing * references to other numerical values with their true * numerical values. */ String flattenPrimitiveLiteralExpression(String expression) { String result = expression; if (configFileParseIsComplete) for (String counterName : arrayCreationCounters.keySet()) { String name = "#" + counterName; if (!containsNameAsWholeWord(result, name)) continue; Number numericalValue = arrayCreationCounters.get(counterName).currentIndex; result = scanForNamesAndReplace(result, name, numericalValue); } else { // Array creation counters are zero. result = removeCounterExpressions(result); } for (String integerDefName : knownIntegerPrimitiveDefinitions.keySet()) { String name = integerDefName; if (!containsNameAsWholeWord(result, name)) continue; Number numericalValue = knownIntegerPrimitiveDefinitions.get(integerDefName).getValue(); result = scanForNamesAndReplace(result, name, numericalValue); } for (String doubleDefName : knownDoublePrimitiveDefinitions.keySet()) { String name = doubleDefName; if (!containsNameAsWholeWord(result, name)) continue; Number numericalValue = knownDoublePrimitiveDefinitions.get(doubleDefName).getValue(); result = scanForNamesAndReplace(result, name, numericalValue); } return result; } private static String scanForNamesAndReplace(String expression, String name, Number value) { int cursor = 0; String replacement = value.toString(), result = expression; int replacementCursorDelta = replacement.length() - name.length(); while (true) { cursor = result.indexOf(name, cursor); if (cursor == -1) break; int nextCharAfterWordIndex = cursor + name.length(); if (nextCharAfterWordIndex == result.length()) { result = result.substring(0, cursor) + replacement; break; } Character nextCharAfterWord = result.charAt(nextCharAfterWordIndex); if (Character.isLetter(nextCharAfterWord) || nextCharAfterWord == '_') ++cursor; else { result = result.substring(0, cursor) + replacement + result.substring(nextCharAfterWordIndex); cursor += replacementCursorDelta; } } return result; } // Replace counter-type tokens ("#name") that appear in an // expression with zeros. private static String removeCounterExpressions(String expression) { String result = expression; for (int i = 0; i < result.length(); ++i) { if (result.charAt(i) == '#') { int j = i + 1; String newNameToRemove = "#"; for (; j < result.length(); ++j) { Character nextChar = result.charAt(j); if (Character.isLetter(nextChar) || nextChar == '_') newNameToRemove += nextChar; else break; } result = result.substring(0, i) + "0" + result.substring(j); j += 1 - newNameToRemove.length(); i = j; } } return result; } private static boolean containsNameAsWholeWord(String expression, String name) { int cursor = expression.indexOf(name, 0); if (cursor == -1) return false; int nextCharAfterWordIndex = cursor + name.length(); if (nextCharAfterWordIndex == expression.length()) return true; Character nextCharAfterWord = expression.charAt(nextCharAfterWordIndex); if (Character.isLetter(nextCharAfterWord) || nextCharAfterWord == '_') return false; else return true; } public static void main(String[] args) { String expression = "Math.exp(#my_first_number/#my_second_number) + #my_first_number**#my_second_number + my_first_number/#my_second_number + my_second_number/#my_first_number"; String expressionCopy = expression; expressionCopy = scanForNamesAndReplace(expressionCopy, "#my_first_number", 1.1); expressionCopy = scanForNamesAndReplace(expressionCopy, "#my_second_number", 2.2); System.out.println(expressionCopy); expressionCopy = expression; expressionCopy = removeCounterExpressions(expressionCopy); System.out.println(expressionCopy); } public DoublePrimitiveExpression hasPrimitiveDoubleValueExpression(String literalName) { if (!knownDoublePrimitiveDefinitions.containsKey(literalName)) throw new NoSuchElementException( "'" + literalName + "' does not have a primitive" + "double definition."); return knownDoublePrimitiveDefinitions.get(literalName); } // Register a general definition expression void registerDefinition(DefinitionExpression newDefinition) { String definitionUniqueName = tryGetDefinitionName(newDefinition); if (newDefinition.isPrimitive()) { if (newDefinition.getObject()[0].getClass() == Integer.class) { knownIntegerPrimitiveDefinitions.put(definitionUniqueName, (IntegerPrimitiveExpression) newDefinition); } else if (newDefinition.getObject()[0].getClass() == Double.class) { knownDoublePrimitiveDefinitions.put(definitionUniqueName, (DoublePrimitiveExpression) newDefinition); } } knownDefinitions.put(definitionUniqueName, newDefinition); definitionExpressionRegistrationOrder.add(newDefinition); Integer arraySize = newDefinition.getTypeExpression().getNumberOfObjectsDeclared().getValue(); if (newDefinition.isPendingUserObjectCreation() && !newDefinition.isReference()) { UserObjectCreationTask newCreationTask = new UserObjectCreationTask(); newCreationTask.uniqueName = definitionUniqueName; newCreationTask.definitionExpression = newDefinition; for (int i = 0; i < arraySize; ++i) userObjectCreationPending.add(newCreationTask); } IntegerIndexOfMaximum newCounter = new IntegerIndexOfMaximum(arraySize); newCounter.currentIndex = 0; arrayCreationCounters.put(definitionUniqueName, newCounter); } // Register a primitive integer definition expression void registerDefinition(IntegerPrimitiveExpression newDefinition) { knownIntegerPrimitiveDefinitions.put(tryGetDefinitionName(newDefinition), newDefinition); this.registerDefinition(newDefinition); } // Register a name declaration void registerNameDeclaration(NameDeclarationExpression newDeclaration) { knownNameDeclarations.put(newDeclaration.getLiteralName(), newDeclaration); } private String tryGetDefinitionName(DefinitionExpression definition) { return definition.isAnonymousDefinition() ? "0" + java.util.UUID.randomUUID().toString() : definition.getLinkedNameDeclaration().getLiteralName(); } final class ObjectCreationRequest extends ConfigurationContext.ObjectCreationRequest { private DefinitionExpression owningDefinitionExpression; ObjectCreationRequest(String typeName, String declaredName, List<ConfigurationContext.Argument> arguments, DefinitionExpression owningDefinitionExpression) { super(typeName, declaredName, arguments); if (owningDefinitionExpression == null) throw new NullArgumentException(); this.owningDefinitionExpression = owningDefinitionExpression; } @Override public void set(Object creationResult) { IntegerIndexOfMaximum arrayCounter = arrayCreationCounters.get(this.getDeclaredName()); if (arrayCounter.isComplete()) throw new IllegalStateException("FromFileConfigurationContext: this object array has already been " + "populated. Details follow\n: " + this.toString()); owningDefinitionExpression.setObject(creationResult, arrayCounter.currentIndex); arrayCounter.increment(); FromFileConfigurationContext.this.awaitingUserCreationRequest = false; } } /** Get the next user object creation request. */ @Override public ObjectCreationRequest nextCreationRequest() { if (awaitingUserCreationRequest) throw new IllegalStateException("The configuration process has encountered a referenced object " + "with no user definition. The method ObjectCreationRequest.set() " + "should be called upon completion of each request."); if (userObjectCreationPending.size() == 0) return null; UserObjectCreationTask nextPendingUserTask = userObjectCreationPending.pollFirst(); ArrayList<ConfigurationContext.Argument> arguments = new ArrayList<ConfigurationContext.Argument>(); CommaListExpression commaListExpression = (CommaListExpression) nextPendingUserTask.definitionExpression; List<ArgumentAssignmentExpression> argAssignmentExpressions = commaListExpression .getArgumentAssignmentExpressions(); for (ArgumentAssignmentExpression argExpression : argAssignmentExpressions) { String literalArgumentName = argExpression.getAssigneeExpression().getLiteralArgumentName(); Object createdObject = argExpression.getDefinitionExpression().getObject()[0]; if (createdObject == null) throw new IllegalStateException("The configuration process has encountered a referenced object " + "with no user definition. The method ObjectCreationRequest.set() " + "should be called upon completion of each request."); ConfigurationContext.Argument newArgument = ConfigurationContext.createArgument(literalArgumentName, createdObject); arguments.add(newArgument); } FromFileConfigurationContext.ObjectCreationRequest result = new FromFileConfigurationContext.ObjectCreationRequest( nextPendingUserTask.definitionExpression.getTypeExpression().getLiteralTypename(), nextPendingUserTask.uniqueName, arguments, nextPendingUserTask.definitionExpression); this.awaitingUserCreationRequest = true; return result; } /** * Returns a brief description of this object. The exact details of the * string are subject to change, however the following is typical: * * 'from-file configuration context with 8 compound expressions and ' * '10 definitions'. */ @Override public String toString() { return "from-file configuration context with " + compoundConfigurationExpressions.size() + " compound expressions " + "and " + knownDefinitions.size() + " definitions."; } void printStdoutVerboseSummary() { System.out.println(toString()); System.out.println("interpretation:"); for (CompoundExpression expression : compoundConfigurationExpressions) System.out.println(expression.toString()); System.out.println("known names:"); for (String name : knownNameDeclarations.keySet()) System.out.println(name); System.out.println("\nall known definitions:"); System.out.println(String.format("%-70s %-50s %-50s", "Unique name", "Type", "Value")); for (Map.Entry<String, DefinitionExpression> record : knownDefinitions.entrySet()) { String name = record.getKey(); Object objValue = record.getValue().getObject()[0]; System.out.println(String.format("%-70s %-50s %-50s", (name.charAt(0) != '0' ? name : "Anonymous def. " + name.toString()), record.getValue().getTypeExpression().getSizeIndexedLiteralTypename(), (objValue == null ? "[pending user definition]" : objValue.toString()))); } System.out.println("\nuser creation requests pending:"); { int index = 1; for (UserObjectCreationTask pending : userObjectCreationPending) { String name = pending.uniqueName; Object objValue = pending.definitionExpression.getObject()[0]; System.out.println(String.format("%5d %-64s %-50s %-50s", index, (name.charAt(0) != '0' ? name : "Anonymous def. " + name.toString()), pending.definitionExpression.getTypeExpression().getSizeIndexedLiteralTypename(), (objValue == null ? "[pending user definition]" : objValue.toString()))); ++index; } } } }