/* $Id: UserDefinedProfile.java 18925 2010-12-19 08:57:33Z thn $
*****************************************************************************
* Copyright (c) 2007,2010 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:
* maurelio1234 - Initial implementation
* thn
* euluis
*****************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 2007-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.profile;
import java.awt.Image;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.swing.ImageIcon;
import org.apache.log4j.Logger;
import org.argouml.cognitive.Critic;
import org.argouml.cognitive.Decision;
import org.argouml.cognitive.ToDoItem;
import org.argouml.cognitive.Translator;
import org.argouml.kernel.ProfileConfiguration;
import org.argouml.model.Model;
import org.argouml.profile.internal.ocl.CrOCL;
import org.argouml.profile.internal.ocl.InvalidOclException;
import org.argouml.uml.cognitive.UMLDecision;
/**
* Represents a profile defined by the user
*
* @author maurelio1234
*/
public class UserDefinedProfile extends Profile {
/**
* Logger.
*/
private static final Logger LOG = Logger
.getLogger(UserDefinedProfile.class);
private String displayName;
private File modelFile;
private Collection profilePackages = null;
private Object packageLock = new Object();
private UserDefinedFigNodeStrategy figNodeStrategy
= new UserDefinedFigNodeStrategy();
private ProfileReference reference;
private ProfileManager profileManager;
private boolean criticsLoaded = false;
private class UserDefinedFigNodeStrategy implements FigNodeStrategy {
private Map<String, Image> images = new HashMap<String, Image>();
public Image getIconForStereotype(Object stereotype) {
return images.get(Model.getFacade().getName(stereotype));
}
/**
* Adds a new descriptor to this strategy
*
* @param fnd
*/
public void addDesrciptor(FigNodeDescriptor fnd) {
images.put(fnd.stereotype, fnd.img);
}
}
private class FigNodeDescriptor {
private String stereotype;
private Image img;
private String src;
private int length;
/**
* @return if this descriptor is valid
*/
public boolean isValid() {
return stereotype != null && src != null && length > 0;
}
}
/**
* The default constructor for this class
*
* @param file the file from where the model should be read
* @param manager the profile manager which will be used to resolve
* dependencies
* @throws ProfileException if the profile could not be loaded
*/
public UserDefinedProfile(File file, ProfileManager manager)
throws ProfileException {
LOG.info("load " + file);
displayName = file.getName();
modelFile = file;
try {
reference = new UserProfileReference(file.getPath());
} catch (MalformedURLException e) {
throw new ProfileException(
"Failed to create the ProfileReference.", e);
}
profileManager = manager;
}
/**
* The default constructor for this class
*
* @param file the file from where the model should be read
* @throws ProfileException if the profile could not be loaded
* @deprecated for 0.30 by euluis. Use
* {@link UserDefinedProfile#UserDefinedProfile(File, ProfileManager)}
* instead.
*/
@Deprecated
public UserDefinedProfile(File file) throws ProfileException {
this(file, getSomeProfileManager());
}
private static class NullProfileManager implements ProfileManager {
public void addSearchPathDirectory(String path) {
}
public void addToDefaultProfiles(Profile profile) {
}
public void applyConfiguration(ProfileConfiguration pc) {
}
public List<Profile> getDefaultProfiles() {
return new ArrayList<Profile>();
}
public Profile getProfileForClass(String className) {
return null;
}
public List<Profile> getRegisteredProfiles() {
return new ArrayList<Profile>();
}
public List<String> getSearchPathDirectories() {
return new ArrayList<String>();
}
public Profile getUMLProfile() {
return null;
}
public Profile lookForRegisteredProfile(String profile) {
return null;
}
public void refreshRegisteredProfiles() {
}
public void registerProfile(Profile profile) {
}
public void removeFromDefaultProfiles(Profile profile) {
}
public void removeProfile(Profile profile) {
}
public void removeSearchPathDirectory(String path) {
}
}
/**
* A constructor that reads a file from an URL
*
* @param url the URL
* @param manager the profile manager which will be used to resolve
* dependencies
* @throws ProfileException if the profile can't be read or is not valid
*/
public UserDefinedProfile(URL url, ProfileManager manager)
throws ProfileException {
LOG.info("load " + url);
reference = new UserProfileReference(url.getPath(), url);
profileManager = manager;
}
/**
* A constructor that reads a file from an URL
*
* @param url the URL
* @throws ProfileException if the profile could not be loaded
* @deprecated for 0.30 by euluis. Use
* {@link UserDefinedProfile#UserDefinedProfile(URL, ProfileManager)}
* instead.
*/
@Deprecated
public UserDefinedProfile(URL url) throws ProfileException {
this(url, getSomeProfileManager());
}
/**
* A constructor that reads a file from an URL associated with some
* profiles. Designed for use with URLs which represent entries in a
* JAR or Zip file.
*
* @param dn the display name of the profile
* @param url the URL of the profile mode
* @param critics the Critics defined by this profile
* @param dependencies the dependencies of this profile
* @param manager the profile manager which will be used to resolve
* dependencies
* @throws ProfileException if the model cannot be loaded
*/
public UserDefinedProfile(String dn, URL url, Set<Critic> critics,
Set<String> dependencies, ProfileManager manager)
throws ProfileException {
LOG.info("load " + url);
this.displayName = dn;
reference = new UserProfileReference(url.getPath(), url);
this.setCritics(critics);
for (String profileID : dependencies) {
addProfileDependency(profileID);
}
profileManager = manager;
}
/**
* A constructor that reads a file from an URL associated with some profiles
*
* @param dn the display name of the profile
* @param url the URL of the profile mode
* @param critics the Critics defined by this profile
* @param dependencies the dependencies of this profile
* @throws ProfileException if the model cannot be loaded
*
* @deprecated for 0.30 by euluis. Use
* {@link UserDefinedProfile#UserDefinedProfile(String, URL, Set, Set, ProfileManager)}
* instead.
*/
@Deprecated
public UserDefinedProfile(String dn, URL url, Set<Critic> critics,
Set<String> dependencies) throws ProfileException {
this(dn, url, critics, dependencies, getSomeProfileManager());
}
private static ProfileManager getSomeProfileManager() {
if (ProfileFacade.isInitiated()) {
return ProfileFacade.getManager();
}
return new NullProfileManager();
}
/**
* Reads the informations defined as TaggedValues
* @param manager the profile manager which will be used to resolve
* dependencies
*/
private void loadModel() {
synchronized (packageLock) {
if (profilePackages == null) {
try {
if (modelFile != null) {
profilePackages = new FileModelLoader()
.loadModel(reference);
} else {
profilePackages = new URLModelLoader()
.loadModel(reference);
}
} catch (ProfileException e1) {
LOG.error("Exception loading profile "
+ reference.getPath(), e1);
profilePackages = Collections.emptySet();
return;
}
}
Collection packagesInProfile = filterPackages(profilePackages);
for (Object obj : packagesInProfile) {
// if there is only one package in the model, we should suppose it's
// the profile model, if there is more than one, we take the ones
// marked as <<profile>>
if (Model.getFacade().isAModelElement(obj)
&& (Model.getFacade().isAProfile(obj)
|| (packagesInProfile.size() == 1))) {
// load profile name
String name = Model.getFacade().getName(obj);
if (name != null) {
displayName = name;
} else {
if (displayName == null) {
displayName = Translator
.localize("misc.profile.unnamed");
}
}
LOG.info("profile " + displayName);
loadDependentProfiles(obj);
}
}
loadFigNodes(packagesInProfile);
}
}
private void loadDependentProfiles(Object obj) {
if (Model.getFacade().getUmlVersion().charAt(0) == '1') {
loadDependentProfilesUml1(obj);
}
// TODO: profile dependencies for UML2
}
/**
* For ArgoUML UML 1.4 profiles dependencies are encoded as a list of
* profile names in the TaggedValue named "Dependency" on the profile pkg.
*
* @param pkg profile package
*/
private void loadDependentProfilesUml1(Object pkg) {
// load profile dependencies
String dependencyListStr = Model.getFacade().getTaggedValueValue(pkg,
"Dependency");
StringTokenizer st = new StringTokenizer(dependencyListStr, " ,;:");
String dependencyName = null;
while (st.hasMoreTokens()) {
dependencyName = st.nextToken();
if (dependencyName != null) {
LOG.debug("Adding dependency " + dependencyName);
Profile profile = profileManager
.lookForRegisteredProfile(dependencyName);
if (profile != null) {
addProfileDependency(profile);
} else {
LOG.warn("The profile \"" + displayName
+ "\" has a dependency named \"" + dependencyName
+ "\" which isn't solvable.");
}
}
}
}
/**
* Load FigNodes from profile packages.
*
* @param packagesInProfile
*/
private void loadFigNodes(Collection packagesInProfile) {
Collection allStereotypes = Model.getExtensionMechanismsHelper()
.getStereotypes(packagesInProfile);
for (Object stereotype : allStereotypes) {
Collection tags = Model.getFacade().getTaggedValuesCollection(
stereotype);
for (Object tag : tags) {
String tagName = Model.getFacade().getTag(tag);
if (tagName == null) {
LOG.debug("profile package with stereotype "
+ Model.getFacade().getName(stereotype)
+ " contains a null tag definition");
} else if (tagName.toLowerCase().equals("figure")) {
LOG.debug("AddFigNode "
+ Model.getFacade().getName(stereotype));
String value = Model.getFacade().getValueOfTag(tag);
File f = new File(value);
FigNodeDescriptor fnd = null;
try {
fnd = loadImage(Model.getFacade().getName(stereotype)
.toString(), f);
figNodeStrategy.addDesrciptor(fnd);
} catch (IOException e) {
LOG.error("Error loading FigNode", e);
}
}
}
}
}
@Override
public Set<Critic> getCritics() {
if (!criticsLoaded ) {
Set<Critic> myCritics = super.getCritics();
myCritics.addAll(getAllCritiquesInModel());
this.setCritics(myCritics);
criticsLoaded = true;
}
return super.getCritics();
}
/**
* @return the packages in the <code>profilePackages</code>
*/
private Collection filterPackages(Collection packages) {
Collection ret = new ArrayList();
// TODO: All this profile loading/handling needs to move someplace in model subsystem probably
for (Object object: packages) {
if (Model.getFacade().isAPackage(object)) {
ret.add(object);
}
}
return ret;
}
private CrOCL generateCriticFromComment(Object critique) {
String ocl = "" + Model.getFacade().getBody(critique);
String headline = null;
String description = null;
int priority = ToDoItem.HIGH_PRIORITY;
List<Decision> supportedDecisions = new ArrayList<Decision>();
List<String> knowledgeTypes = new ArrayList<String>();
String moreInfoURL = null;
Collection tags = Model.getFacade().getTaggedValuesCollection(critique);
boolean i18nFound = false;
for (Object tag : tags) {
if (Model.getFacade().getTag(tag).toLowerCase().equals("i18n")) {
i18nFound = true;
String i18nSource = Model.getFacade().getValueOfTag(tag);
headline = Translator.localize(i18nSource + "-head");
description = Translator.localize(i18nSource + "-desc");
moreInfoURL = Translator.localize(i18nSource + "-moreInfoURL");
} else if (!i18nFound
&& Model.getFacade().getTag(tag).toLowerCase().equals(
"headline")) {
headline = Model.getFacade().getValueOfTag(tag);
} else if (!i18nFound
&& Model.getFacade().getTag(tag).toLowerCase().equals(
"description")) {
description = Model.getFacade().getValueOfTag(tag);
} else if (Model.getFacade().getTag(tag).toLowerCase().equals(
"priority")) {
priority = str2Priority(Model.getFacade().getValueOfTag(tag));
} else if (Model.getFacade().getTag(tag).toLowerCase().equals(
"supporteddecision")) {
String decStr = Model.getFacade().getValueOfTag(tag);
StringTokenizer st = new StringTokenizer(decStr, ",;:");
while (st.hasMoreTokens()) {
Decision decision = str2Decision(st.nextToken().trim()
.toLowerCase());
if (decision != null) {
supportedDecisions.add(decision);
}
}
} else if (Model.getFacade().getTag(tag).toLowerCase().equals(
"knowledgetype")) {
String ktStr = Model.getFacade().getValueOfTag(tag);
StringTokenizer st = new StringTokenizer(ktStr, ",;:");
while (st.hasMoreTokens()) {
String knowledge = str2KnowledgeType(st.nextToken().trim()
.toLowerCase());
if (knowledge != null) {
knowledgeTypes.add(knowledge);
}
}
} else if (!i18nFound
&& Model.getFacade().getTag(tag).toLowerCase().equals(
"moreinfourl")) {
moreInfoURL = Model.getFacade().getValueOfTag(tag);
}
}
LOG.debug("OCL-Critic: " + ocl);
try {
return new CrOCL(ocl, headline, description, priority,
supportedDecisions, knowledgeTypes, moreInfoURL);
} catch (InvalidOclException e) {
LOG.error("Invalid OCL in XMI!", e);
return null;
}
}
private String str2KnowledgeType(String token) {
String knowledge = null;
if (token.equals("completeness")) {
knowledge = Critic.KT_COMPLETENESS;
}
if (token.equals("consistency")) {
knowledge = Critic.KT_CONSISTENCY;
}
if (token.equals("correctness")) {
knowledge = Critic.KT_CORRECTNESS;
}
if (token.equals("designers")) {
knowledge = Critic.KT_DESIGNERS;
}
if (token.equals("experiencial")) {
knowledge = Critic.KT_EXPERIENCIAL;
}
if (token.equals("optimization")) {
knowledge = Critic.KT_OPTIMIZATION;
}
if (token.equals("organizational")) {
knowledge = Critic.KT_ORGANIZATIONAL;
}
if (token.equals("presentation")) {
knowledge = Critic.KT_PRESENTATION;
}
if (token.equals("semantics")) {
knowledge = Critic.KT_SEMANTICS;
}
if (token.equals("syntax")) {
knowledge = Critic.KT_SYNTAX;
}
if (token.equals("tool")) {
knowledge = Critic.KT_TOOL;
}
return knowledge;
}
private int str2Priority(String prioStr) {
int prio = ToDoItem.MED_PRIORITY;
if (prioStr.toLowerCase().equals("high")) {
prio = ToDoItem.HIGH_PRIORITY;
} else if (prioStr.toLowerCase().equals("med")) {
prio = ToDoItem.MED_PRIORITY;
} else if (prioStr.toLowerCase().equals("low")) {
prio = ToDoItem.LOW_PRIORITY;
} else if (prioStr.toLowerCase().equals("interruptive")) {
prio = ToDoItem.INTERRUPTIVE_PRIORITY;
}
return prio;
}
private Decision str2Decision(String token) {
Decision decision = null;
if (token.equals("behavior")) {
decision = UMLDecision.BEHAVIOR;
}
if (token.equals("containment")) {
decision = UMLDecision.CONTAINMENT;
}
if (token.equals("classselection")) {
decision = UMLDecision.CLASS_SELECTION;
}
if (token.equals("codegen")) {
decision = UMLDecision.CODE_GEN;
}
if (token.equals("expectedusage")) {
decision = UMLDecision.EXPECTED_USAGE;
}
if (token.equals("inheritance")) {
decision = UMLDecision.INHERITANCE;
}
if (token.equals("instantiation")) {
decision = UMLDecision.INSTANCIATION;
}
if (token.equals("methods")) {
decision = UMLDecision.METHODS;
}
if (token.equals("modularity")) {
decision = UMLDecision.MODULARITY;
}
if (token.equals("naming")) {
decision = UMLDecision.NAMING;
}
if (token.equals("patterns")) {
decision = UMLDecision.PATTERNS;
}
if (token.equals("plannedextensions")) {
decision = UMLDecision.PLANNED_EXTENSIONS;
}
if (token.equals("relationships")) {
decision = UMLDecision.RELATIONSHIPS;
}
if (token.equals("statemachines")) {
decision = UMLDecision.STATE_MACHINES;
}
if (token.equals("stereotypes")) {
decision = UMLDecision.STEREOTYPES;
}
if (token.equals("storage")) {
decision = UMLDecision.STORAGE;
}
return decision;
}
// TODO: Is this (critics embedded in comments) actually used by anyone?
private List<CrOCL> getAllCritiquesInModel() {
List<CrOCL> ret = new ArrayList<CrOCL>();
Collection<Object> comments = getAllCommentsInModel(getProfilePackages());
for (Object comment : comments) {
if (Model.getExtensionMechanismsHelper().hasStereotype(comment,
"Critic")) {
CrOCL cr = generateCriticFromComment(comment);
if (cr != null) {
ret.add(cr);
}
}
}
return ret;
}
@SuppressWarnings("unchecked")
private Collection<Object> getAllCommentsInModel(Collection objs) {
Collection<Object> col = new ArrayList<Object>();
for (Object obj : objs) {
if (Model.getFacade().isAComment(obj)) {
col.add(obj);
} else if (Model.getFacade().isANamespace(obj)) {
Collection contents = Model
.getModelManagementHelper().getAllContents(obj);
if (contents != null) {
col.addAll(contents);
}
}
}
return col;
}
/**
* @return the string that should represent this profile in the GUI.
*/
public String getDisplayName() {
// TODO: Seems like overkill to load the model just to get the display
// name, but that's where it's stored currently - tfm
loadModel();
return displayName;
}
/**
* Returns null. This profile has no formatting strategy.
*
* @return null.
*/
@Override
public FormatingStrategy getFormatingStrategy() {
return null;
}
/**
* Returns null. This profile has no figure strategy.
*
* @return null.
*/
@Override
public FigNodeStrategy getFigureStrategy() {
return figNodeStrategy;
}
/**
* @return the file passed at the constructor
*/
public File getModelFile() {
return modelFile;
}
/**
* @return the name of the model and the file name
*/
@Override
public String toString() {
File str = getModelFile();
return super.toString() + (str != null ? " [" + str + "]" : "");
}
@Override
public Collection getProfilePackages() {
loadModel();
return profilePackages;
}
@Override
public Collection getLoadedPackages() {
if (profilePackages == null) {
return Collections.emptySet();
} else {
return Collections.unmodifiableCollection(profilePackages);
}
}
private FigNodeDescriptor loadImage(String stereotype, File f)
throws IOException {
FigNodeDescriptor descriptor = new FigNodeDescriptor();
descriptor.length = (int) f.length();
descriptor.src = f.getPath();
descriptor.stereotype = stereotype;
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(f));
byte[] buf = new byte[descriptor.length];
try {
bis.read(buf);
} catch (IOException e) {
e.printStackTrace();
}
descriptor.img = new ImageIcon(buf).getImage();
return descriptor;
}
}