/* $Id: Designer.java 17813 2010-01-12 18:33:21Z linus $ ***************************************************************************** * Copyright (c) 2009 Contributors - see below * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * tfmorris ***************************************************************************** * * Some portions of this file was previously release using the BSD License: */ // Copyright (c) 1996-2008 The Regents of the University of California. All // Rights Reserved. Permission to use, copy, modify, and distribute this // software and its documentation without fee, and without a written // agreement is hereby granted, provided that the above copyright notice // and this paragraph appear in all copies. This software program and // documentation are copyrighted by The Regents of the University of // California. The software program and documentation are supplied "AS // IS", without any accompanying services from The Regents. The Regents // does not warrant that the operation of the program will be // uninterrupted or error-free. The end-user understands that the program // was developed for research purposes and is advised not to rely // exclusively on the program for any reason. IN NO EVENT SHALL THE // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. package org.argouml.cognitive; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Properties; import javax.swing.Action; import javax.swing.Icon; import org.apache.log4j.Logger; import org.argouml.application.api.Argo; import org.argouml.configuration.Configuration; import org.argouml.configuration.ConfigurationKey; import org.argouml.model.InvalidElementException; import org.tigris.gef.util.ChildGenerator; import org.tigris.gef.util.EnumerationEmpty; /** * This class models the designer who is building a complex design in * some application domain and needs continuous feedback to aid in the * making of good design decisions.<p> * * <strong>This area needs work, especially as it is a * central idea of Argo.</strong><p> * * Currently (almost) everything is hardcoded. What can be configurable??<p> * * The ToDoList is dependent on this class, * i.e. each designer has its ToDoList.<p> * * Each designer has his own Agency, * which is the only class that knows all the critics.<p> * * This class listens to property changes from ...?<p> * * A designer can create ToDo Items, just like the critics. Hence the designer * implements the Poster interface.<p> * * TODO: There is a strong dependency cycle between Agency and Designer. They * either need to be merged into a single class or partitioned differently, * perhaps using an interface to break the cycle. The Designer singleton gets * passed to almost every single part of the Critic subsystem, creating strong * coupling throughout. - tfm 20070620 * * @author Jason Robbins */ public final class Designer implements Poster, Runnable, PropertyChangeListener { /** * Logger. */ private static final Logger LOG = Logger.getLogger(Designer.class); /** * the singleton of this class. */ private static Designer theDesignerSingleton = new Designer(); private static boolean userWorking; private static List<Decision> unspecifiedDecision; private static List<Goal> unspecifiedGoal; private static Action saveAction; static { unspecifiedDecision = new ArrayList<Decision>(); unspecifiedDecision.add(Decision.UNSPEC); unspecifiedGoal = new ArrayList<Goal>(); unspecifiedGoal.add(Goal.getUnspecifiedGoal()); } /** * The key to remember persistently the latest choice made * for the menuitem Toggle Auto-Critique. */ public static final ConfigurationKey AUTO_CRITIQUE = Configuration.makeKey("cognitive", "autocritique"); //////////////////////////////////////////////////////////////// // instance variables /** * ToDoList items that are on the designers ToDoList because of * this material. */ private ToDoList toDoList; /** * Preferences -- very ill defined. */ private Properties prefs; /** * The designerName is the name of the current user, as he can enter in the * menuitem Edit->Settings...->User->Full Name.<p> * * The designerName gets updated when the user enters a new name. */ private String designerName; /** * The decisions currently being considered by the designer.<p> * * Decisions are currently modeled as simple descriptive strings.<p> * * Each decision also has a priority number which is ill defined, * but positive Ints mean that the designer is considering it. This * explicit representation of what decisions the designer is * interested in at a given moment allows the Agency to select * relevant critics for execution. */ private DecisionModel decisions; /** * The goals of the designer are likewise used by the Agency to * determine what critics are relevant. */ private GoalModel goals; /** * Each designer has their own Agency instance that is responsible * for selecting and executing critics that are relevant to this * designer on an on going basis. */ private Agency agency; /** * The clarifying icon for this poster. */ private Icon clarifier; private Thread critiquerThread; private int critiquingInterval; private int critiqueCPUPercent; /** * dm's that should be critiqued ASAP. */ private List<Object> hotQueue; private List<Long> hotReasonQueue; private List<Object> addQueue; private List<Long> addReasonQueue; private List<Object> removeQueue; private static int longestAdd; private static int longestHot; /** * dm's that should be critiqued relatively soon. */ private List<Object> warmQueue; private ChildGenerator childGenerator; private static Object critiquingRoot; private long critiqueDuration; private int critiqueLock; private static PropertyChangeSupport pcs; /** * Property Names. */ public static final String MODEL_TODOITEM_ADDED = "MODEL_TODOITEM_ADDED"; /** * Property Names. */ public static final String MODEL_TODOITEM_DISMISSED = "MODEL_TODOITEM_DISMISSED"; //////////////////////////////////////////////////////////////// // constructor and singeton methods /** * The constructor. */ private Designer() { decisions = new DecisionModel(); goals = new GoalModel(); agency = new Agency(); prefs = new Properties(); toDoList = new ToDoList(); toDoList.spawnValidityChecker(this); userWorking = false; critiquingInterval = 8000; critiqueCPUPercent = 10; hotQueue = new ArrayList<Object>(); hotReasonQueue = new ArrayList<Long>(); addQueue = new ArrayList<Object>(); addReasonQueue = new ArrayList<Long>(); removeQueue = new ArrayList<Object>(); longestAdd = 0; longestHot = 0; warmQueue = new ArrayList<Object>(); childGenerator = new EmptyChildGenerator(); critiqueLock = 0; } /** * @return the designer singleton */ public static Designer theDesigner() { return theDesignerSingleton; } //////////////////////////////////////////////////////////////// // critiquing /** * Start a separate thread to continually select and execute * critics that are relevant to this designer's work. * * @param root the rootobject the critiques will check */ public void spawnCritiquer(Object root) { /* TODO: really should be a separate class */ critiquerThread = new Thread(this, "CritiquingThread"); critiquerThread.setDaemon(true); critiquerThread.setPriority(Thread.currentThread().getPriority() - 1); critiquerThread.start(); critiquingRoot = root; } /** * Continuously select and execute critics against this designer's * design. {@link #spawnCritiquer(Object)} is used to start a * Thread that runs this. */ public void run() { try { while (true) { // local variables - what do they do? long critiqueStartTime; long cutoffTime; int minWarmElements = 5; int size; // the critiquing thread should wait if disabled. synchronized (this) { while (!Configuration.getBoolean( Designer.AUTO_CRITIQUE, true)) { try { this.wait(); } catch (InterruptedException ignore) { LOG.error("InterruptedException!!!", ignore); } } } // why? if (critiquingRoot != null // && getAutoCritique() && critiqueLock <= 0) { // why? synchronized (this) { critiqueStartTime = System.currentTimeMillis(); cutoffTime = critiqueStartTime + 3000; size = addQueue.size(); for (int i = 0; i < size; i++) { hotQueue.add(addQueue.get(i)); hotReasonQueue.add(addReasonQueue.get(i)); } addQueue.clear(); addReasonQueue.clear(); longestHot = Math.max(longestHot, hotQueue.size()); agency.determineActiveCritics(this); while (hotQueue.size() > 0) { Object dm = hotQueue.get(0); Long reasonCode = hotReasonQueue.get(0); hotQueue.remove(0); hotReasonQueue.remove(0); Agency.applyAllCritics(dm, theDesigner(), reasonCode.longValue()); } size = removeQueue.size(); for (int i = 0; i < size; i++) { warmQueue.remove(removeQueue.get(i)); } removeQueue.clear(); if (warmQueue.size() == 0) { warmQueue.add(critiquingRoot); } while (warmQueue.size() > 0 && (System.currentTimeMillis() < cutoffTime || minWarmElements > 0)) { if (minWarmElements > 0) { minWarmElements--; } Object dm = warmQueue.get(0); warmQueue.remove(0); try { Agency.applyAllCritics(dm, theDesigner()); java.util.Enumeration subDMs = childGenerator.gen(dm); while (subDMs.hasMoreElements()) { Object nextDM = subDMs.nextElement(); if (!(warmQueue.contains(nextDM))) { warmQueue.add(nextDM); } } } catch (InvalidElementException e) { // Don't let a transient error kill the thread LOG.warn("Element " + dm + "caused an InvalidElementException. " + "Ignoring for this pass."); } } } } else { critiqueStartTime = System.currentTimeMillis(); } critiqueDuration = System.currentTimeMillis() - critiqueStartTime; long cycleDuration = (critiqueDuration * 100) / critiqueCPUPercent; long sleepDuration = Math.min(cycleDuration - critiqueDuration, 3000); sleepDuration = Math.max(sleepDuration, 1000); LOG.debug("sleepDuration= " + sleepDuration); try { Thread.sleep(sleepDuration); } catch (InterruptedException ignore) { LOG.error("InterruptedException!!!", ignore); } } } catch (Exception e) { LOG.error("Critic thread killed by exception", e); } } /** * A modelelement has been changed. * Now we give it priority to be checked by the critics ASAP. * * TODO: why is is synchronised? * TODO: what about when objects are first created? * * @param dm the design material * @param reason the reason */ public synchronized void critiqueASAP(Object dm, String reason) { long rCode = Critic.reasonCodeFor(reason); if (!userWorking) { return; } // TODO: Should we be doing anything on deleted elements? // This throws an exception on remove events. - skip for now - tfm if ("remove".equals(reason)) { return; } LOG.debug("critiqueASAP:" + dm); int addQueueIndex = addQueue.indexOf(dm); if (addQueueIndex == -1) { addQueue.add(dm); Long reasonCodeObj = new Long(rCode); addReasonQueue.add(reasonCodeObj); } else { Long reasonCodeObj = addReasonQueue.get(addQueueIndex); long rc = reasonCodeObj.longValue() | rCode; Long newReasonCodeObj = new Long(rc); addReasonQueue.set(addQueueIndex, newReasonCodeObj); } removeQueue.add(dm); longestAdd = Math.max(longestAdd, addQueue.size()); } /** * Look for potential problems or open issues in the given design. * This is currently done by invoking the Agency. * * @param des the design to be checked */ public void critique(Object des) { Agency.applyAllCritics(des, this); } /** * Adds a property change listener. * * @param pcl * The property change listener to add */ public static void addListener(PropertyChangeListener pcl) { if (pcs == null) { pcs = new PropertyChangeSupport(theDesigner()); } LOG.debug("addPropertyChangeListener(" + pcl + ")"); pcs.addPropertyChangeListener(pcl); } /** * Removes a property change listener. * * @param p * The class to remove as a property change listener. */ public static void removeListener(PropertyChangeListener p) { if (pcs != null) { LOG.debug("removePropertyChangeListener()"); pcs.removePropertyChangeListener(p); } } /** * Setter for saveAction. * * @param theSaveAction The new saveAction. */ public static void setSaveAction(Action theSaveAction) { saveAction = theSaveAction; } /** * @param property the property name * @param oldValue the old value * @param newValue the new value */ public static void firePropertyChange(String property, Object oldValue, Object newValue) { if (pcs != null) { pcs.firePropertyChange(property, oldValue, newValue); } if (MODEL_TODOITEM_ADDED.equals(property) || MODEL_TODOITEM_DISMISSED.equals(property)) { if (saveAction != null) { saveAction.setEnabled(true); } } } /* * Performs critique asap. * * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) */ public void propertyChange(PropertyChangeEvent pce) { if (pce.getPropertyName().equals(Argo.KEY_USER_FULLNAME.getKey())) { designerName = pce.getNewValue().toString(); } else { critiqueASAP(pce.getSource(), pce.getPropertyName()); } } //////////////////////////////////////////////////////////////// // criticism control /** * Ask this designer's agency to select which critics should be active. */ public void determineActiveCritics() { agency.determineActiveCritics(this); } //////////////////////////////////////////////////////////////// // accessors /** * autoCritique and critiquingInterval are two prameters that * control how the critiquing thread operates. If autoCritique is * false then now critiquing is done in the background. The * critiquingInterval determines how often the critiquing thread * executes. The concept of an interval between runs will become * less important as Argo is redesigned to be more trigger * driven. * * @return autoCritique */ public boolean getAutoCritique() { return Configuration.getBoolean(Designer.AUTO_CRITIQUE, true); } /** * @see #getAutoCritique() * @param b true to set auto critique on, false for off */ public void setAutoCritique(boolean b) { Configuration.setBoolean(Designer.AUTO_CRITIQUE, b); synchronized (this) { if (b) { this.notifyAll(); } } } /** * Get the Critiquing interval. * * @return The interval. */ public int getCritiquingInterval() { return critiquingInterval; } /** * Set the Critiquing Interval. * * @param i The new interval. */ public void setCritiquingInterval(int i) { critiquingInterval = i; } /** * Disable critiquing. */ public static void disableCritiquing() { synchronized (theDesigner()) { theDesigner().critiqueLock++; } } /** * Enable critiquing. */ public static void enableCritiquing() { synchronized (theDesigner()) { theDesigner().critiqueLock--; } } /** * Clear all critiquing results. */ public static void clearCritiquing() { synchronized (theDesigner()) { theDesigner().toDoList.removeAllElements(); //v71 theDesigner().hotQueue.clear(); theDesigner().hotReasonQueue.clear(); theDesigner().addQueue.clear(); theDesigner().addReasonQueue.clear(); theDesigner().removeQueue.clear(); theDesigner().warmQueue.clear(); } //clear out queues! @@@ } /** * @param d the critiquing root */ public static void setCritiquingRoot(Object d) { synchronized (theDesigner()) { critiquingRoot = d; } /* Don't clear everything here, breaks loading! */ } /** * @return the critiquing root */ public static Object getCritiquingRoot() { synchronized (theDesigner()) { return critiquingRoot; } } /** * @return the childgenerator */ public ChildGenerator getChildGenerator() { return childGenerator; } /** * @param cg the childgenerator */ public void setChildGenerator(ChildGenerator cg) { childGenerator = cg; } /** * @return the decisions */ public DecisionModel getDecisionModel() { return decisions; } /** * @return the goals */ public GoalModel getGoalModel() { return goals; } /** * @return the goals. */ public List<Goal> getGoalList() { return goals.getGoalList(); } /** * This method returns true.<p> * * ToDoItem's that are posted by the designer are assumed to be * valid until the designer explicitly removes them. Perhaps in the * future the designer could specify a condition to determine when * his items expire. * * @see ToDoItem * @see org.argouml.cognitive.Critic#stillValid * * @see org.argouml.cognitive.Poster#stillValid( * org.argouml.cognitive.ToDoItem, org.argouml.cognitive.Designer) * * @param i the todo item * @param d the designer * @return true if still valid */ public boolean stillValid(ToDoItem i, Designer d) { return true; } /* * @see org.argouml.cognitive.Poster#supports(org.argouml.cognitive.Decision) */ public boolean supports(Decision d) { return d == Decision.UNSPEC; } /* * @see org.argouml.cognitive.Poster#getSupportedDecisions() */ public List<Decision> getSupportedDecisions() { return unspecifiedDecision; } /* * @see org.argouml.cognitive.Poster#supports(org.argouml.cognitive.Goal) */ public boolean supports(Goal g) { return true; } /* * @see org.argouml.cognitive.Poster#getSupportedGoals() */ public List<Goal> getSupportedGoals() { return unspecifiedGoal; } /* * @see org.argouml.cognitive.Poster#containsKnowledgeType(java.lang.String) */ public boolean containsKnowledgeType(String type) { return type.equals("Designer's"); } /* * Just returns the descr param. * * @see org.argouml.cognitive.Poster#expand(java.lang.String, ListSet) */ public String expand(String desc, ListSet offs) { return desc; } /* * Get the generic clarifier for this designer/poster. * * @see org.argouml.cognitive.Poster#getClarifier() */ public Icon getClarifier() { return clarifier; } /** * Get the generic clarifier for this designer/poster. * * @param clar the clarifier icon */ public void setClarifier(Icon clar) { clarifier = clar; } /** * @return this Designer's ToDoList, a list of pending problems and * issues that the designer might be interested in. * * @see ToDoList */ public ToDoList getToDoList() { return toDoList; } /** * Remove all the items in the given list from my list. * * @param list the items to be removed */ public void removeToDoItems(ToDoList list) { toDoList.removeAll(list); } /** * Reply the designers personal preferences. * Currently not used (?). * * @return the preferences */ public Properties getPrefs() { return prefs; } /** * @param d the decision * @return true if the given decision is considered */ public boolean isConsidering(Decision d) { return d.getPriority() > 0; } /** * Record the extent to which the designer is considering the given * decision. * * @param decision the decision * @param priority the priority */ public void setDecisionPriority(String decision, int priority) { decisions.setDecisionPriority(decision, priority); } /** * Record the extent to which the designer desires the given goal. * * @param goal the given goal * @return true if this goal is desired */ public boolean hasGoal(String goal) { return goals.hasGoal(goal); } /** * @param goal the given goal * @param priority the priority */ public void setGoalPriority(String goal, int priority) { goals.setGoalPriority(goal, priority); } /** * @param goal the goal I (me, the designer) desire */ public void startDesiring(String goal) { goals.startDesiring(goal); } /** * @param goal the goal that is not desired any more */ public void stopDesiring(String goal) { goals.stopDesiring(goal); } /* * @see org.argouml.cognitive.Poster#snooze() */ public void snooze() { /* do nothing */ } /* * @see org.argouml.cognitive.Poster#unsnooze() */ public void unsnooze() { /* do nothing */ } /** * Reply the Agency object that is helping this Designer. * * @return my agency */ public Agency getAgency() { return agency; } //////////////////////////////////////////////////////////////// // user interface /** * Inform the human designer using this system that the given * ToDoItem should be considered. This can be disruptive if the item * is urgent, or (more commonly) it is added to his ToDoList so that * he can consider it at his leisure. * * @param item the todo item */ public void inform(ToDoItem item) { toDoList.addElement(item); } /** * set the name of this designer. * @param name the designer name */ public void setDesignerName(String name) { designerName = name; } /** * query the name of the designer. * @return the designer name */ public String getDesignerName() { return designerName; } /* * This is used in the todo panel, when "By Poster" is chosen for a * manually created todo item. * * @see java.lang.Object#toString() */ @Override public String toString() { //TODO: This should be the name of the designer that created // the todoitem, not the current username! return getDesignerName(); } //////////////////////////////////////////////////////////////// // issue resolution /* * Does not do anything. * * @see org.argouml.cognitive.Poster#fixIt(org.argouml.cognitive.ToDoItem, * java.lang.Object) */ public void fixIt(ToDoItem item, Object arg) { } /* * Just returns false. * * @see org.argouml.cognitive.Poster#canFixIt(org.argouml.cognitive.ToDoItem) */ public boolean canFixIt(ToDoItem item) { return false; } /** * @param working true if the user is working * (i.e. this is not the startup phase of ArgoUML) */ public static void setUserWorking(boolean working) { userWorking = working; } /** * @return true if the user is working * (i.e. this is not the startup phase of ArgoUML) */ public static boolean isUserWorking() { return userWorking; } /** * ChildGenerator which always returns an empty enumeration. * @author MarkusK * */ static class EmptyChildGenerator implements ChildGenerator { /** * Reply a Enumeration of the children of the given Object. * * @param o The object. * @return the Enumeration. */ public Enumeration gen(Object o) { return EnumerationEmpty.theInstance(); } /** * The UID. */ private static final long serialVersionUID = 7599621170029351645L; } /* end class ChildGenDMElements */ /** * The UID. */ private static final long serialVersionUID = -3647853023882216454L; }