/*******************************************************************************
* Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors
* 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://robocode.sourceforge.net/license/epl-v10.html
*
* Contributors:
* Pavel Savara
* - Initial implementation
* Flemming N. Larsen
* - Extended to support both class path and source path
*******************************************************************************/
package net.sf.robocode.repository.items;
import net.sf.robocode.io.FileUtil;
import net.sf.robocode.io.Logger;
import net.sf.robocode.io.URLJarCollector;
import static net.sf.robocode.io.Logger.logError;
import net.sf.robocode.repository.IRobotRepositoryItem;
import net.sf.robocode.repository.RobotType;
import net.sf.robocode.repository.root.ClassPathRoot;
import net.sf.robocode.repository.root.IRepositoryRoot;
import net.sf.robocode.security.HiddenAccess;
import net.sf.robocode.core.Container;
import net.sf.robocode.host.IHostManager;
import net.sf.robocode.version.IVersionManager;
import robocode.control.RobotSpecification;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author Pavel Savara (original)
* @author Flemming N. Larsen (contributor)
*/
public class RobotItem extends NamedItem implements IRobotRepositoryItem {
private static final long serialVersionUID = 1L;
// Allowed maximum length for a robot's full package name
private final static int MAX_FULL_PACKAGE_NAME_LENGTH = 32;
// Allowed maximum length for a robot's short class name
private final static int MAX_SHORT_CLASS_NAME_LENGTH = 32;
private final static String ROBOT_CLASSNAME = "robot.classname";
private final static String ROBOT_VERSION = "robot.version";
protected final static String ROBOT_LANGUAGE = "robot.language";
private final static String ROBOT_DESCRIPTION = "robot.description";
private final static String ROBOT_AUTHOR_NAME = "robot.author.name";
private final static String ROBOT_WEBPAGE = "robot.webpage";
// private final static String ROBOT_AUTHOR_EMAIL = "robot.author.email";
// private final static String ROBOT_AUTHOR_WEBSITE = "robot.author.website";
private final static String ROBOCODE_VERSION = "robocode.version";
private final static String ROBOT_INCLUDE_SOURCE = "robot.include.source";
// File extensions
private static final String CLASS_EXTENSION = ".class";
private static final String PROPERTIES_EXTENSION = ".properties";
private static final String HTML_EXTENSION = ".html";
private static final boolean ALWAYS_USE_CACHE_FOR_DATA = System.getProperty("ALWAYSUSECACHEFORDATA", "false").equals(
"true");
RobotType robotType;
private URL classPathURL;
private Set<URL> sourcePathURLs; // This is a Set in order to avoid duplicates
private URL classURL;
private URL propertiesURL;
private String className;
protected boolean isPropertiesLoaded;
public RobotItem(URL itemURL, IRepositoryRoot root) {
super(itemURL, root);
isValid = true;
classPathURL = root.getURL();
sourcePathURLs = new HashSet<URL>();
}
private void populate() {
populatePropertiesURLFromClassURL();
populateClassURLFromPropertiesURL();
loadProperties();
}
public void setClassURL(URL classUrl) {
this.classURL = classUrl;
populate();
}
public void setPropertiesURL(URL propertiesUrl) {
this.propertiesURL = propertiesUrl;
populate();
}
public void setClassPathURL(URL classPathUrl) {
this.classPathURL = classPathUrl;
}
public void addSourcePathURL(URL sourcePathUrl) {
sourcePathURLs.add(sourcePathUrl);
}
// -------------------------------------
// URL initialization
// -------------------------------------
private void populatePropertiesURLFromClassURL() {
if (propertiesURL == null && classURL != null) {
final String path = classURL.toString().replaceFirst("\\" + CLASS_EXTENSION, PROPERTIES_EXTENSION);
try {
propertiesURL = new URL(path);
} catch (MalformedURLException e) {
Logger.logError(e);
}
if (isInvalidURL(propertiesURL)) {
propertiesURL = null;
}
}
}
private void populateClassURLFromPropertiesURL() {
if (classURL == null && propertiesURL != null) {
final String path = propertiesURL.toString().replaceAll("\\" + PROPERTIES_EXTENSION, CLASS_EXTENSION);
try {
classURL = new URL(path);
} catch (MalformedURLException e) {
Logger.logError(e);
}
if (isInvalidURL(classURL)) {
classURL = null;
}
}
}
private void populateClassNameFromClassURL() {
populate();
if (className == null && classURL != null) {
final String path = classURL.toString();
// .dll file?
int index = (path.toLowerCase().indexOf(".dll!/"));
if (index > 0) {
className = path.substring(index + 6);
return;
}
// Class within .jar or regular file path?
index = path.lastIndexOf('!');
if (index > 0) {
// .jar file
className = path.substring(index + 2);
} else {
// file path
className = path.substring(root.getURL().toString().length());
}
// Remove the file extension from the class name
index = className.lastIndexOf('.');
if (index > 0) {
className = className.substring(0, index);
}
// Replace all file separators with dots (from file path the package/namespace)
className = className.replaceAll("[\\\\\\/]", ".");
}
}
private void populateHtmlURL() {
populate();
if (htmlURL == null && classURL != null) {
try {
htmlURL = new URL(classURL.toString().replaceFirst(CLASS_EXTENSION, HTML_EXTENSION));
} catch (MalformedURLException ignore) {}
}
if (htmlURL == null && propertiesURL != null) {
try {
htmlURL = new URL(propertiesURL.toString().replaceFirst(PROPERTIES_EXTENSION, HTML_EXTENSION));
} catch (MalformedURLException ignore) {}
}
if (isInvalidURL(htmlURL)) {
htmlURL = null;
}
}
private boolean isInvalidURL(URL url) {
if (url != null) {
InputStream is = null;
try {
URLConnection conn = URLJarCollector.openConnection(url);
is = conn.getInputStream();
return false;
} catch (IOException e) {
return true;
} finally {
FileUtil.cleanupStream(is);
}
}
return true;
}
// / -------------------------------------
// / public
// / -------------------------------------
public boolean isTeam() {
return false;
}
public URL getHtmlURL() {
// Lazy
if (htmlURL == null) {
populateHtmlURL();
}
return htmlURL;
}
public URL getPropertiesURL() {
if (propertiesURL == null) {
populatePropertiesURLFromClassURL();
}
return propertiesURL;
}
public List<String> getFriendlyURLs() {
populate();
final Set<String> urls = new HashSet<String>();
if (propertiesURL != null) {
final String pUrl = propertiesURL.toString();
final String noType = pUrl.substring(0, pUrl.lastIndexOf('.'));
urls.add(pUrl);
urls.add(noType);
urls.add(propertiesURL.getPath());
}
if (classURL != null) {
final String cUrl = classURL.toString();
final String noType = cUrl.substring(0, cUrl.lastIndexOf('.'));
urls.add(cUrl);
urls.add(noType);
urls.add(classURL.getPath());
}
if (getFullClassName() != null) {
if (System.getProperty("TESTING", "false").equals("true")) {
urls.add(getFullClassName());
} else {
urls.add(getUniqueFullClassName());
}
urls.add(getUniqueFullClassNameWithVersion());
}
if (root.isJAR()) {
urls.add(root.getURL().toString());
}
if (root instanceof ClassPathRoot) {
String friendly = ((ClassPathRoot) root).getFriendlyProjectURL(itemURL);
if (friendly != null) {
urls.add(friendly);
}
}
return new ArrayList<String>(urls);
}
public void update(long lastModified, boolean force) {
if (lastModified > this.lastModified || force) {
if (force) {
isValid = true;
}
// trying to guess all correct file locations
populate();
this.lastModified = lastModified;
if (classURL == null) {
isValid = false;
}
loadProperties();
if (root.isJAR() && !isPropertiesLoaded) {
isValid = false;
}
if (isValid) {
validateType(false);
}
if (isValid) {
verifyName();
}
}
}
protected void validateType(boolean resolve) {
populate();
final IHostManager hostManager = Container.getComponent(IHostManager.class);
robotType = hostManager.getRobotType(this, resolve, classURL != null);
if (!robotType.isValid()) {
isValid = false;
}
}
// Stronger than update
public boolean validate() {
validateType(true);
return isValid;
}
// Note that ROBOCODE_CLASSNAME can be invalid, an hence can't be trusted when loaded!
// Hence, we read the fullClassName field instead and NOT the ROBOCODE_CLASSNAME property.
private boolean loadProperties() {
if (!isPropertiesLoaded && propertiesURL != null) {
InputStream ios = null;
try {
URLConnection con = URLJarCollector.openConnection(propertiesURL);
ios = con.getInputStream();
properties.load(ios);
isPropertiesLoaded = true;
return true;
} catch (IOException e) {
return false;
} finally {
FileUtil.cleanupStream(ios);
}
}
return false;
}
private boolean verifyName() {
String robotName = getFullClassName();
String shortClassName = getShortClassName();
final boolean valid = verifyRobotName(robotName, shortClassName, false);
if (!valid) {
isValid = false;
}
return valid;
}
public static boolean verifyRobotName(String fullClassName, String shortClassName, boolean silent) {
if (fullClassName == null || fullClassName.length() == 0 || fullClassName.contains("$")) {
return false;
}
int lIndex = fullClassName.indexOf(".");
if (lIndex > 0) {
String rootPackage = fullClassName.substring(0, lIndex);
if (rootPackage.equalsIgnoreCase("robocode")) {
if (!silent) {
logError("Robot " + fullClassName + " ignored. You cannot use package " + rootPackage);
}
return false;
}
if (rootPackage.length() > MAX_FULL_PACKAGE_NAME_LENGTH) {
if (!silent) {
logError(
"Robot " + fullClassName + " has package name too long. " + MAX_FULL_PACKAGE_NAME_LENGTH
+ " characters maximum please.");
}
return false;
}
}
if (shortClassName != null && shortClassName.length() > MAX_SHORT_CLASS_NAME_LENGTH) {
if (!silent) {
logError(
"Robot " + fullClassName + " has classname too long. " + MAX_SHORT_CLASS_NAME_LENGTH
+ " characters maximum please.");
}
return false;
}
return true;
}
public void storeProperties(OutputStream os, boolean includeSource, String version, String desc, String author, URL web) throws IOException {
if (className != null) {
properties.setProperty(ROBOT_CLASSNAME, className);
}
if (version != null) {
properties.setProperty(ROBOT_VERSION, version);
}
if (desc != null) {
properties.setProperty(ROBOT_DESCRIPTION, desc);
}
if (author != null) {
properties.setProperty(ROBOT_AUTHOR_NAME, author);
}
if (web != null) {
properties.setProperty(ROBOT_WEBPAGE, web.toString());
}
properties.setProperty(ROBOCODE_VERSION, Container.getComponent(IVersionManager.class).getVersion());
properties.setProperty(ROBOT_INCLUDE_SOURCE, "" + includeSource);
saveProperties(os);
saveProperties();
}
private void saveProperties(OutputStream os) throws IOException {
properties.store(os, "Robocode Robot");
}
private void saveProperties() {
File file = new File(root.getPath(), className.replaceAll("\\.", "/") + PROPERTIES_EXTENSION);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
saveProperties(fos);
} catch (IOException e) {
e.printStackTrace();
} finally {
FileUtil.cleanupStream(fos);
}
populatePropertiesURLFromClassURL();
}
public boolean isDroid() {
return robotType.isDroid();
}
public boolean isTeamRobot() {
return robotType.isTeamRobot();
}
public boolean isAdvancedRobot() {
return robotType.isAdvancedRobot();
}
public boolean isStandardRobot() {
return robotType.isStandardRobot();
}
public boolean isInteractiveRobot() {
return robotType.isInteractiveRobot();
}
public boolean isPaintRobot() {
return robotType.isPaintRobot();
}
public boolean isJuniorRobot() {
return robotType.isJuniorRobot();
}
public URL getClassPathURL() {
return classPathURL;
}
public URL[] getSourcePathURLs() {
// If no source path URL exists, we must use the class path URL
if (sourcePathURLs.size() == 0) {
return new URL[] { classPathURL };
}
return sourcePathURLs.toArray(new URL[] {});
}
public String getFullClassName() {
// Lazy
if (className == null) {
populateClassNameFromClassURL();
}
return className;
}
public String getVersion() {
return properties.getProperty(ROBOT_VERSION, null);
}
public String getDescription() {
return properties.getProperty(ROBOT_DESCRIPTION, null);
}
public String getAuthorName() {
return properties.getProperty(ROBOT_AUTHOR_NAME, null);
}
public String getRobotLanguage() {
final String lang = properties.getProperty(ROBOT_LANGUAGE, null);
return lang == null ? "java" : lang;
}
public URL getWebpage() {
try {
return new URL(properties.getProperty(ROBOT_WEBPAGE, null));
} catch (MalformedURLException e) {
return null;
}
}
public boolean getIncludeSource() {
return properties.getProperty(ROBOT_INCLUDE_SOURCE, "true").equalsIgnoreCase("true");
}
public boolean isSourceIncluded() {
return sourcePathURLs.size() > 0;
}
public String getRobocodeVersion() {
return properties.getProperty(ROBOCODE_VERSION, null);
}
public String getReadableDirectory() {
if (getRootPackage() == null) {
return null;
}
String dir;
if (root.isJAR()) {
String jarFile = getClassPathURL().getFile();
jarFile = jarFile.substring(jarFile.lastIndexOf('/') + 1, jarFile.length());
dir = FileUtil.getRobotsDataDir().getPath();
if (jarFile.length() > 0) {
dir += File.separator + jarFile + '_';
}
dir += File.separator;
} else {
dir = getClassPathURL().getFile();
}
return dir + getRootPackage();
}
public String getWritableDirectory() {
if (getRootPackage() == null) {
return null;
}
File dir;
if (root.isJAR()) {
String jarFile = getClassPathURL().getFile();
jarFile = jarFile.substring(jarFile.lastIndexOf('/') + 1, jarFile.length());
dir = FileUtil.getRobotsDataDir();
if (jarFile.length() > 0) {
dir = new File(dir, File.separator + jarFile + '_');
}
} else {
dir = ALWAYS_USE_CACHE_FOR_DATA ? FileUtil.getRobotsDataDir() : root.getPath();
}
return dir + File.separator + getFullPackage().replace('.', File.separatorChar);
}
public RobotSpecification createRobotSpecification(RobotSpecification battleRobotSpec, String teamId) {
RobotSpecification specification;
if (battleRobotSpec != null) {
specification = battleRobotSpec;
} else {
specification = createRobotSpecification();
}
if (teamId != null) {
HiddenAccess.setTeamId(specification, teamId);
}
return specification;
}
public String toString() {
return itemURL.toString();
}
}