You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
549 lines
15 KiB
549 lines
15 KiB
package de.srsoftware.web4rail; |
|
|
|
import java.security.MessageDigest; |
|
import java.security.NoSuchAlgorithmException; |
|
import java.util.AbstractMap; |
|
import java.util.ArrayList; |
|
import java.util.Date; |
|
import java.util.HashMap; |
|
import java.util.HashSet; |
|
import java.util.List; |
|
import java.util.Map; |
|
import java.util.Random; |
|
import java.util.Set; |
|
|
|
import org.json.JSONObject; |
|
import org.slf4j.Logger; |
|
import org.slf4j.LoggerFactory; |
|
|
|
import de.keawe.tools.translations.Translation; |
|
import de.srsoftware.tools.Tag; |
|
import de.srsoftware.web4rail.Plan.Direction; |
|
import de.srsoftware.web4rail.actions.Action; |
|
import de.srsoftware.web4rail.conditions.Condition; |
|
import de.srsoftware.web4rail.moving.Car; |
|
import de.srsoftware.web4rail.moving.Locomotive; |
|
import de.srsoftware.web4rail.moving.Train; |
|
import de.srsoftware.web4rail.tags.Button; |
|
import de.srsoftware.web4rail.tags.Fieldset; |
|
import de.srsoftware.web4rail.tags.Form; |
|
import de.srsoftware.web4rail.tags.Input; |
|
import de.srsoftware.web4rail.tags.Table; |
|
import de.srsoftware.web4rail.tags.TextArea; |
|
import de.srsoftware.web4rail.tags.Window; |
|
import de.srsoftware.web4rail.tiles.Block; |
|
import de.srsoftware.web4rail.tiles.Contact; |
|
import de.srsoftware.web4rail.tiles.Tile; |
|
|
|
/** |
|
* @author Stephan Richter, SRSoftware 2020…2021 |
|
*/ |
|
public abstract class BaseClass implements Constants{ |
|
protected static Plan plan; // the track layout in use |
|
public static final Random random = new Random(); |
|
public static String speedUnit = DEFAULT_SPEED_UNIT; |
|
public static String lengthUnit = DEFAULT_LENGTH_UNIT; |
|
private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray(); |
|
protected Id id = null; |
|
protected String notes; |
|
private static HashMap<Id,BaseClass> registry = new HashMap<BaseClass.Id, BaseClass>(); |
|
private static HashMap<Class<? extends BaseClass>,Set<String>> customFieldNames = new HashMap<Class<? extends BaseClass>, Set<String>>(); |
|
|
|
public static final Logger LOG = LoggerFactory.getLogger(BaseClass.class); |
|
private static final String CUSTOM_FIELDS = "custom_Fields"; |
|
private static final String NEW_CUSTOM_FIELD_NAME = "new_custom_field_name"; |
|
protected HashMap<String,String> customFieldValues = new HashMap<String, String>(); |
|
private BaseClass parent; |
|
|
|
public static class Context { |
|
|
|
private BaseClass main = null; |
|
private Tile tile; |
|
private Block block; |
|
private Train train; |
|
private Route route; |
|
private Action action; |
|
private Condition condition; |
|
private Car car; |
|
private Contact contact; |
|
private Direction direction; |
|
|
|
public Context(BaseClass object) { |
|
setMain(object); |
|
} |
|
|
|
public Action action() { |
|
return action; |
|
} |
|
|
|
public Block block() { |
|
return block; |
|
} |
|
|
|
public Context block(Block newBlock) { |
|
LOG.debug("{}.block({})",this,newBlock); |
|
block = newBlock; |
|
return this; |
|
} |
|
|
|
public Car car() { |
|
return car; |
|
} |
|
|
|
public void clear() { |
|
action = null; |
|
block = null; |
|
car = null; |
|
condition = null; |
|
contact = null; |
|
direction = null; |
|
main = null; |
|
route = null; |
|
tile = null; |
|
train = null; |
|
} |
|
|
|
public Context clone() { |
|
Context clone = new Context(main); |
|
clone.action = action; |
|
clone.block = block; |
|
clone.car = car; |
|
clone.condition = condition; |
|
clone.contact = contact; |
|
clone.direction = direction; |
|
clone.route = route; |
|
clone.tile = tile; |
|
clone.train = train; |
|
return clone; |
|
} |
|
|
|
public Condition condition() { |
|
return condition; |
|
} |
|
|
|
public Contact contact() { |
|
return contact; |
|
} |
|
|
|
public Context contact(Contact newContact) { |
|
contact = newContact; |
|
return this; |
|
} |
|
|
|
|
|
public Direction direction() { |
|
return direction; |
|
} |
|
|
|
public Context direction(Direction newDirection) { |
|
LOG.debug("{}.direction({})",this,newDirection); |
|
direction = newDirection; |
|
return this; |
|
} |
|
|
|
public boolean invalidated() { |
|
return isNull(main); |
|
} |
|
|
|
public Context setMain(BaseClass object) { |
|
main = object; |
|
LOG.debug("{}.setMain({})",this,object); |
|
if (main instanceof Tile) this.tile = (Tile) main; |
|
if (main instanceof Contact) this.contact = (Contact) main; |
|
if (main instanceof Block) this.block = (Block) main; |
|
if (main instanceof Train) this.train = (Train) main; |
|
if (main instanceof Route) this.route = (Route) main; |
|
if (main instanceof Action) this.action = (Action) main; |
|
if (main instanceof Condition) this.condition = (Condition) main; |
|
if (main instanceof Car) this.car = (Car) main; |
|
return this; |
|
} |
|
|
|
public Route route() { |
|
return route; |
|
} |
|
|
|
public Context route(Route newRoute) { |
|
route = newRoute; |
|
return this; |
|
} |
|
|
|
public Tile tile() { |
|
return tile; |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
StringBuffer sb = new StringBuffer(getClass().getSimpleName()); |
|
sb.append("("); |
|
sb.append("main: "+main); |
|
if (isSet(train)) sb.append(", "+t("Train: {}",train)); |
|
if (isSet(direction)) sb.append(", "+t("Direction: {}",direction)); |
|
if (isSet(block)) sb.append(", "+t("Block: {}",block)); |
|
if (isSet(route)) sb.append(", "+t("Route: {}",route)); |
|
if (isSet(contact)) sb.append(", "+t("Contact: {}",contact)); |
|
sb.append(")"); |
|
return sb.toString(); |
|
} |
|
|
|
public Train train() { |
|
return train; |
|
} |
|
|
|
public Context train(Train newTrain) { |
|
LOG.debug("{}.train({})",this,newTrain); |
|
train = newTrain; |
|
return this; |
|
} |
|
} |
|
|
|
public class FormInput extends ArrayList<Map.Entry<String, Tag>>{ |
|
|
|
private static final long serialVersionUID = -2371203388908395216L; |
|
|
|
public FormInput add(String caption,Tag tag) { |
|
add(new AbstractMap.SimpleEntry<String,Tag>(caption,tag)); |
|
return this; |
|
} |
|
} |
|
|
|
public static class Id implements CharSequence, Comparable<Id>{ |
|
private String internalId; |
|
|
|
public Id() { |
|
try { |
|
Thread.sleep(1); |
|
} catch (InterruptedException e) { |
|
e.printStackTrace(); |
|
} |
|
internalId = ""+new Date().getTime(); |
|
} |
|
|
|
|
|
public Id(String id) { |
|
internalId = id; |
|
} |
|
|
|
@Override |
|
public int hashCode() { |
|
return internalId.hashCode(); |
|
} |
|
|
|
@Override |
|
public boolean equals(Object other) { |
|
if (other == null) return false; |
|
if (this == other) return true; |
|
return internalId.equals(other.toString()); |
|
} |
|
|
|
public static Id from(JSONObject json) { |
|
return Id.from(json,ID); |
|
} |
|
|
|
public static Id from(JSONObject json,String key) { |
|
return json.has(key) ? new Id(""+json.get(key)) : null; |
|
} |
|
|
|
public static Id from(Map<String,String> params) { |
|
return Id.from(params,ID); |
|
} |
|
|
|
|
|
public static Id from(Map<String, String> params, String key) { |
|
String sid = params.get(key); |
|
return sid == null ? null : new Id(sid); |
|
} |
|
|
|
@Override |
|
public char charAt(int index) { |
|
return internalId.charAt(index); |
|
} |
|
|
|
|
|
@Override |
|
public int length() { |
|
return internalId.length(); |
|
} |
|
|
|
|
|
@Override |
|
public CharSequence subSequence(int begin, int end) { |
|
return internalId.subSequence(begin, end); |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
return internalId; |
|
} |
|
|
|
|
|
@Override |
|
public int compareTo(Id other) { |
|
return internalId.compareTo(other.internalId); |
|
} |
|
} |
|
|
|
public Button button(String text,Map<String,String> additionalProps) { |
|
return new Button(text,props(additionalProps)); |
|
} |
|
|
|
public Button button(String text) { |
|
return button(text,null); |
|
} |
|
|
|
public Form form(String id,List<Map.Entry<String, Tag>> elements) { |
|
Form form = new Form(id); |
|
|
|
Table table = new Table(); |
|
for (Map.Entry<String, Tag>entry : elements) { |
|
String key = entry.getKey(); |
|
Tag val = entry.getValue(); |
|
if (isNull(key) && val instanceof Input) ((Input)val).hideIn(form); |
|
table.addRow(isSet(key)?key:"",entry.getValue()); |
|
} |
|
|
|
table.addTo(form); |
|
|
|
new Button(t("Apply"),form).addTo(form); |
|
return form; |
|
} |
|
|
|
@SuppressWarnings("unchecked") |
|
public static <T extends BaseClass> T get(Id id) { |
|
return (T) registry.get(id); |
|
} |
|
|
|
public Id id() { |
|
if (isNull(id)) id = new Id(); |
|
return id; |
|
} |
|
|
|
public static boolean isNull(Object o) { |
|
return o==null; |
|
} |
|
|
|
public static boolean isSet(Object o) { |
|
return o != null; |
|
} |
|
|
|
public JSONObject json() { |
|
JSONObject json = new JSONObject(); |
|
if (isSet(id)) json.put(ID, id().toString()); |
|
if (isSet(notes) && !notes.isEmpty()) json.put(NOTES, notes); |
|
Set<String> customFieldNames = BaseClass.customFieldNames.get(getClass()); |
|
JSONObject customFields = null; |
|
if (isSet(customFieldNames)) for (String fieldName : customFieldNames){ |
|
String val = customFieldValues.get(fieldName); |
|
if (isSet(val) && !val.trim().isEmpty()) { |
|
if (isNull(customFields)) customFields = new JSONObject(); |
|
customFields.put(fieldName, val); |
|
} |
|
} |
|
if (isSet(customFields)) json.put(CUSTOM_FIELDS, customFields); |
|
|
|
return json; |
|
} |
|
|
|
public Tag link(String tagClass,Object caption) { |
|
return link(tagClass,caption,null); |
|
} |
|
|
|
public Tag link(String tagClass,Object caption,Map<String,String> additionalProps) { |
|
Tag link = link(tagClass,caption.toString(),props(additionalProps)); |
|
if (isSet(notes) && !notes.isEmpty()) link.title(notes); |
|
return link; |
|
} |
|
|
|
public static Tag link(String tagClass,String caption,Map<String,String> props) { |
|
String json = new JSONObject(props).toString().replace("\"", "'"); |
|
return new Tag(tagClass).clazz("link").attr("onclick","request("+json+")").content(caption.toString()); |
|
} |
|
|
|
@SuppressWarnings("unchecked") |
|
public static <T extends BaseClass> List<T> listElements(Class<T> cls) { |
|
ArrayList<T> result = new ArrayList<T>(); |
|
for (BaseClass object : registry.values()) { |
|
if (isSet(object) && cls.isAssignableFrom(object.getClass())) { |
|
result.add((T) object); |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
public BaseClass load(JSONObject json) { |
|
if (json.has(ID)) { |
|
id = Id.from(json); |
|
register(); |
|
} |
|
if (json.has(NOTES)) notes = json.getString(NOTES); |
|
if (json.has(CUSTOM_FIELDS)) { |
|
JSONObject customFields = json.getJSONObject(CUSTOM_FIELDS); |
|
for (String fieldName : customFields.keySet()) { |
|
String val = customFields.getString(fieldName); |
|
Set<String> customFieldNames = BaseClass.customFieldNames.get(getClass()); |
|
if (isNull(customFieldNames)) { |
|
customFieldNames = new HashSet<String>(); |
|
BaseClass.customFieldNames.put(getClass(),customFieldNames); |
|
} |
|
customFieldNames.add(fieldName); |
|
customFieldValues.put(fieldName, val); |
|
} |
|
} |
|
return this; |
|
} |
|
|
|
public static String md5sum(Object o) { |
|
try { |
|
MessageDigest digest = MessageDigest.getInstance("MD5"); |
|
digest.update(o.toString().getBytes(UTF8)); |
|
StringBuffer sb = new StringBuffer(); |
|
for (byte b : digest.digest()) { |
|
sb.append(HEX_CHARS[(b & 0xF0) >> 4]); |
|
sb.append(HEX_CHARS[b & 0x0F]); |
|
} |
|
return sb.toString(); |
|
} catch (NoSuchAlgorithmException e) { |
|
return null; |
|
} |
|
} |
|
|
|
public static HashMap<String, String> merged(Map<String, String> base, Map<String, String> overlay) { |
|
HashMap<String,String> merged = new HashMap<>(base); |
|
overlay.entrySet().stream().forEach(entry -> merged.put(entry.getKey(), entry.getValue())); |
|
return merged; |
|
} |
|
|
|
public BaseClass parent() { |
|
return parent; |
|
} |
|
|
|
@SuppressWarnings("unchecked") |
|
public <T extends BaseClass> T parent(BaseClass parent) { |
|
this.parent = parent; |
|
return (T) this; |
|
} |
|
|
|
public Window properties() { |
|
return properties(new ArrayList<>(), new FormInput(), new ArrayList<>()); |
|
} |
|
|
|
|
|
protected Window properties(List<Fieldset> preForm,FormInput formInputs,List<Fieldset> postForm) { |
|
Window win = new Window(getClass().getSimpleName()+"-properties", t("Properties of {}",this.title())); |
|
|
|
preForm.forEach(fieldset -> fieldset.addTo(win)); |
|
|
|
formInputs.add(new AbstractMap.SimpleEntry<String, Tag>(null,new Input(ACTION, ACTION_UPDATE))); |
|
formInputs.add(new AbstractMap.SimpleEntry<String, Tag>(null,new Input(REALM,realm()))); |
|
formInputs.add(new AbstractMap.SimpleEntry<String, Tag>(null,new Input(ID,id()))); |
|
|
|
formInputs.add(new AbstractMap.SimpleEntry<String, Tag>(t("Notes"),new TextArea(NOTES,notes))); |
|
|
|
form(getClass().getSimpleName()+"-prop-form",formInputs) |
|
.addTo(new Fieldset(t("Basic properties"))) |
|
.addTo(win); |
|
|
|
postForm.forEach(fieldset -> fieldset.addTo(win)); |
|
|
|
Fieldset customFields = new Fieldset(t("custom fields")); |
|
customFields.attr("id", "custom"); |
|
|
|
Form customForm = new Form(CUSTOM_FIELDS); |
|
new Input(ACTION, ACTION_UPDATE).hideIn(customForm); |
|
new Input(REALM,realm()).hideIn(customForm); |
|
new Input(ID,id()).hideIn(customForm); |
|
|
|
Table table = new Table(); |
|
|
|
Set<String> fieldNames = customFieldNames.get(getClass()); |
|
if (isSet(fieldNames)) for (String fieldName : fieldNames) { |
|
String val = customFieldValues.get(fieldName); |
|
table.addRow(fieldName,new Input(fieldName,isNull(val) ? "" : val)); |
|
} |
|
|
|
table.addRow(t("Add new custom field"),new Input(NEW_CUSTOM_FIELD_NAME)); |
|
table.addTo(customForm); |
|
new Button(t("Apply"),customForm).addTo(customForm).addTo(customFields); |
|
customFields.addTo(win); |
|
|
|
return win; |
|
} |
|
|
|
public String realm() { |
|
if (this instanceof Contact) return REALM_CONTACT; |
|
if (this instanceof Tile) return REALM_PLAN; |
|
|
|
if (this instanceof Locomotive) return REALM_LOCO; |
|
if (this instanceof Car) return REALM_CAR; |
|
|
|
if (this instanceof Action) return REALM_ACTIONS; |
|
if (this instanceof Condition) return REALM_CONDITION; |
|
if (this instanceof Route) return REALM_ROUTE; |
|
if (this instanceof Train) return REALM_TRAIN; |
|
return REALM_PLAN; |
|
} |
|
|
|
private String title() { |
|
return toString(); |
|
} |
|
|
|
private Map<String,String> props(Map<String,String> additionalProps){ |
|
HashMap<String,String> props = new HashMap<String, String>(Map.of(REALM, realm(), ACTION, ACTION_PROPS, ID, id().toString())); |
|
if (isSet(additionalProps)) props.putAll(additionalProps); |
|
return props; |
|
} |
|
|
|
public BaseClass register() { |
|
registry.put(id(),this); |
|
return this; |
|
} |
|
|
|
public BaseClass remove() { |
|
LOG.debug("BaseClass.Remove {} ({})",id(),this); |
|
if (isSet(parent)) parent.removeChild(this); |
|
return registry.remove(id()); |
|
} |
|
|
|
protected void removeChild(BaseClass child) {} |
|
|
|
public static void resetRegistry() { |
|
registry = new HashMap<BaseClass.Id, BaseClass>(); |
|
customFieldNames = new HashMap<Class<? extends BaseClass>, Set<String>>(); |
|
} |
|
|
|
|
|
protected static String t(String txt, Object...fills) { |
|
if (isSet(fills)) for (int i=0; i<fills.length; i++) { |
|
if ("\\".equals(fills[i])) fills[i]="\\\\"; |
|
} |
|
return Translation.get(Application.class, txt, fills); |
|
} |
|
|
|
public static long timestamp() { |
|
return new Date().getTime(); |
|
} |
|
|
|
public BaseClass unregister() { |
|
return registry.remove(this.id()); |
|
} |
|
|
|
protected Object update(HashMap<String, String> params) { |
|
LOG.debug("update: {}",params); |
|
if (params.containsKey(NOTES)) notes = params.get(NOTES).trim(); |
|
String newCustomFieldName = params.get(NEW_CUSTOM_FIELD_NAME); |
|
Set<String> fieldNames = customFieldNames.get(getClass()); |
|
if (isSet(fieldNames)) for (String fieldName : fieldNames) { |
|
String fieldValue = params.get(fieldName); |
|
if (isSet(fieldValue)) customFieldValues.put(fieldName, fieldValue); |
|
} |
|
if (isSet(newCustomFieldName) && !newCustomFieldName.trim().isEmpty()) { |
|
if (isNull(fieldNames)) { |
|
fieldNames = new HashSet<String>(); |
|
customFieldNames.put(getClass(), fieldNames); |
|
} |
|
fieldNames.add(newCustomFieldName); |
|
} |
|
|
|
return this; |
|
} |
|
}
|
|
|