/* This file is part of leafdigital leafChat. leafChat 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. leafChat 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 leafChat. If not, see <http://www.gnu.org/licenses/>. Copyright 2011 Samuel Marshall. */ package com.leafdigital.scripting; import java.util.regex.*; import org.w3c.dom.Element; import com.leafdigital.ui.api.*; import util.xml.*; import leafchat.core.api.*; /** Represents a single item that's part of a script */ public abstract class ScriptItem { /** Parent script */ private Script parent; /** Index within script */ private int index; /** True if item is enabled */ private boolean enabled; /** More than zero if there is an error in the item */ private int errors; /** Error message text */ private String messages; /** * Constructs this item from XML. * @param parent Owning script * @param e XML element that holds all the data for this item * @param index Unique index within script allocated to this item * @throws XMLException */ public ScriptItem(Script parent,Element e,int index) throws XMLException { this.parent=parent; this.index=index; enabled=XML.getRequiredAttribute(e,"enabled").equals("y"); } /** * Constructs a new item. * @param parent Owning script * @param index Unique index within script allocated to this item */ public ScriptItem(Script parent,int index) { this.parent=parent; this.index=index; enabled=true; } /** @return True if item is enabled and should be compiled in */ boolean isEnabled() { return enabled; } /** @return Unique index within script */ int getIndex() { return index; } /** * Alters the enabled state of this item. * @param enabled True to enable, false to disable */ void setEnabled(boolean enabled) { if(enabled==this.enabled) return; this.enabled=enabled; markChanged(); } /** Informs the parent script that this item has changed */ protected void markChanged() { parent.markChanged(); } /** * Saves this item by setting up an element with its data. * @param e Element (initialised with tag name only) */ void save(Element e) { e.setAttribute("enabled",enabled ? "y" : "n"); } /** * Called to get any required import statements for Java source. Default none. * @return Source code or null if none */ String getSourceImports() { return null; } /** * Called to get any required fields for Java source. Default none. * @return Source code or null if none */ String getSourceFields() { return null; } /** * Called to get required code for init method. Default none. * @return Source code or null if none */ String getSourceInit() { return null; } /** * Called to get required code for close method. Default none. * @return Source code or null if none */ String getSourceClose() { return null; } /** * Called to get any methods for Java source. * @return Source code or null if none */ String getSourceMethods() { return null; } /** * Adds user code into the source file, performing all necessary changes * to that code so that it is converted into proper Java and marked up with * its original line numbers. * @param userCode Original code (may be multiple lines) * @return Converted string */ protected String convertUserCode(String userCode) { String[] lines=userCode.split("\n"); StringBuffer sb=new StringBuffer(); for(int i=0;i<lines.length;i++) { sb.append("// ====v"+i+"\n"); String line=lines[i]; if(line.matches("^\\s*/[^/].*$")) { // IRC command sb.append("doCommand("+preprocessIRC(line.trim())+");\n"); } else { sb.append(preprocessJava(line)+"\n"); } sb.append("// ====^"+i+"\n"); } return sb.toString(); } /** * Does preprocessing of Java code to support leafChat-specific syntax: * 1. Allows singleton get to be written as [[Commands]] instead of * context.getSingleton(Commands.class) * 2. There is no #2 (yet) * @param line Original line * @return Converted linee */ private String preprocessJava(String line) { return line.replaceAll("\\[\\[([A-Za-z0-9]+)\\]\\]","(($1)context.getSingleton($1.class))"); } private static Pattern IRCLINEWITHINSERT=Pattern.compile("^(.*?)\\$\\{(.*?)\\}(.*)$"); /** * Process an IRC command line into a double-quoted Java string. * @param line IRC line beginning with / * @return Java quoted string beginning with and ending with " */ private String preprocessIRC(String line) { // Find any variable inserts/Java code ${} Matcher m=IRCLINEWITHINSERT.matcher(line); if(!m.matches()) { // No variables. Just replace " with \" and return the line in a string return "\""+line.replaceAll("\"","\\\"")+"\""; } String before=m.group(1),java=m.group(2),after=m.group(3); return preprocessIRC(before)+"+("+preprocessJava(java)+")+"+preprocessIRC(after); } String getTag() { return getClass().getName().replaceAll("^.*Item","").toLowerCase(); } protected String getTypeName() { return getClass().getName().replaceAll("^.*Item",""); } /** @return XML text for the summary label on item list view */ protected abstract String getSummaryLabel(); protected boolean hasErrors() { return errors>0; } protected String getErrorLabel() { return errors+" error"+(errors!=1 ? "s" :"")+" "+messages; } /** @return Colour to use for striped background in editor */ protected abstract java.awt.Color getNormalStripeRGB(); java.awt.Color getStripeRGB() { if(errors>0) return java.awt.Color.red; else return getNormalStripeRGB(); } private Button okButton; /** * Obtains an editing page for this item. * @param ok Button to enable/disable when settings are (in)valid * @return Page containing item settings for editing */ protected Page getPage(Button ok) { this.okButton = ok; UI u = parent.getContext().getSingle(UI.class); return u.createPage("itemsettings." + getClass().getName().replaceAll("^.*Item","").toLowerCase(), this); } /** * While the editing page is up, enables/disables the OK button. * @param enable Whether to enable or disable */ protected void allowOK(boolean enable) { okButton.setEnabled(enable); } /** Saves settings on the UI for the editing page */ protected abstract void saveSettings(); /** Clears remembered errors within this item */ void clearErrors() { errors=0; messages=""; } final static int NOTINUSERCODE=-1; /** * Marks that an error occurred in this item * @param userCodeLine Line number (0-based) within user code or NOTINUSERCODE * @param message Error message */ void markError(int userCodeLine,String message) { errors++; if(!messages.equals("")) messages+=" "; messages+="\u2022 "+XML.esc(message); } /** @return XML text information about variables provided by this item */ public String getVariablesLabel() { return null; } /** @return Plugin context (for use by subclasses) */ public PluginContext getContext() { return parent.getContext(); } /** * Updates index when other items are deleted * @param index New index */ void setIndex(int index) { this.index=index; } /** * Quotes a string for use in Java * @param s String * @return String surrounded in double quotes with necessary characters escaped */ protected String getQuotedString(String s) { return '"'+s.replaceAll("\\\\","\\\\").replaceAll("\\\"","\\\"")+'"'; } }