| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.felix.gogo.runtime; |
| |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.lang.reflect.Method; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.WeakHashMap; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.CopyOnWriteArraySet; |
| |
| import org.apache.felix.service.command.*; |
| import org.apache.felix.service.threadio.ThreadIO; |
| |
| public class CommandProcessorImpl implements CommandProcessor |
| { |
| protected final Set<Converter> converters = new CopyOnWriteArraySet<>(); |
| protected final Set<CommandSessionListener> listeners = new CopyOnWriteArraySet<>(); |
| protected final ConcurrentMap<String, Map<Object, Integer>> commands = new ConcurrentHashMap<>(); |
| protected final Map<String, Object> constants = new ConcurrentHashMap<>(); |
| protected final ThreadIO threadIO; |
| protected final WeakHashMap<CommandSession, Object> sessions = new WeakHashMap<>(); |
| protected boolean stopped; |
| |
| public CommandProcessorImpl() |
| { |
| this(null); |
| } |
| |
| public CommandProcessorImpl(ThreadIO tio) |
| { |
| threadIO = tio; |
| } |
| |
| @Override |
| public CommandSessionImpl createSession(CommandSession parent) { |
| synchronized (sessions) { |
| if (stopped) |
| { |
| throw new IllegalStateException("CommandProcessor has been stopped"); |
| } |
| if (!sessions.containsKey(parent) || !(parent instanceof CommandSessionImpl)) { |
| throw new IllegalArgumentException(); |
| } |
| CommandSessionImpl session = new CommandSessionImpl(this, (CommandSessionImpl) parent); |
| sessions.put(session, null); |
| return session; |
| } |
| } |
| |
| public CommandSessionImpl createSession(InputStream in, OutputStream out, OutputStream err) |
| { |
| synchronized (sessions) |
| { |
| if (stopped) |
| { |
| throw new IllegalStateException("CommandProcessor has been stopped"); |
| } |
| CommandSessionImpl session = new CommandSessionImpl(this, in, out, err); |
| sessions.put(session, null); |
| return session; |
| } |
| } |
| |
| void closeSession(CommandSessionImpl session) |
| { |
| synchronized (sessions) |
| { |
| sessions.remove(session); |
| } |
| } |
| |
| public void stop() |
| { |
| synchronized (sessions) |
| { |
| stopped = true; |
| // Create a copy, as calling session.close() will remove the session from the map |
| CommandSession[] toClose = this.sessions.keySet().toArray(new CommandSession[this.sessions.size()]); |
| for (CommandSession session : toClose) |
| { |
| session.close(); |
| } |
| // Just in case... |
| sessions.clear(); |
| } |
| } |
| |
| public void addConverter(Converter c) |
| { |
| converters.add(c); |
| } |
| |
| public void removeConverter(Converter c) |
| { |
| converters.remove(c); |
| } |
| |
| public void addListener(CommandSessionListener l) |
| { |
| listeners.add(l); |
| } |
| |
| public void removeListener(CommandSessionListener l) |
| { |
| listeners.remove(l); |
| } |
| |
| public Set<String> getCommands() |
| { |
| return Collections.unmodifiableSet(commands.keySet()); |
| } |
| |
| protected Function getCommand(String name, final Object path) |
| { |
| int colon = name.indexOf(':'); |
| |
| if (colon < 0) |
| { |
| return null; |
| } |
| |
| name = name.toLowerCase(); |
| String cfunction = name.substring(colon); |
| boolean anyScope = (colon == 1 && name.charAt(0) == '*'); |
| |
| Map<Object, Integer> cmdMap = commands.get(name); |
| |
| if (null == cmdMap && anyScope) |
| { |
| String scopePath = (null == path ? "*" : path.toString()); |
| |
| for (String scope : scopePath.split(":")) |
| { |
| if (scope.equals("*")) |
| { |
| for (Entry<String, Map<Object, Integer>> entry : commands.entrySet()) |
| { |
| if (entry.getKey().endsWith(cfunction)) |
| { |
| cmdMap = entry.getValue(); |
| break; |
| } |
| } |
| } |
| else |
| { |
| cmdMap = commands.get(scope + cfunction); |
| if (cmdMap != null) |
| { |
| break; |
| } |
| } |
| } |
| } |
| |
| Object cmd = null; |
| if (cmdMap != null && !cmdMap.isEmpty()) |
| { |
| for (Entry<Object, Integer> e : cmdMap.entrySet()) |
| { |
| if (cmd == null || e.getValue() > cmdMap.get(cmd)) |
| { |
| cmd = e.getKey(); |
| } |
| } |
| } |
| if ((null == cmd) || (cmd instanceof Function)) |
| { |
| return (Function) cmd; |
| } |
| |
| return new CommandProxy(cmd, cfunction.substring(1)); |
| } |
| |
| @Descriptor("add commands") |
| public void addCommand(@Descriptor("scope") String scope, @Descriptor("target") Object target) |
| { |
| Class<?> tc = (target instanceof Class<?>) ? (Class<?>) target : target.getClass(); |
| addCommand(scope, target, tc); |
| } |
| |
| @Descriptor("add commands") |
| public void addCommand(@Descriptor("scope") String scope, @Descriptor("target") Object target, @Descriptor("functions") Class<?> functions) |
| { |
| addCommand(scope, target, functions, 0); |
| } |
| |
| public void addCommand(String scope, Object target, Class<?> functions, int ranking) |
| { |
| if (target == null) |
| { |
| return; |
| } |
| |
| String[] names = getFunctions(functions); |
| for (String function : names) |
| { |
| addCommand(scope, target, function, ranking); |
| } |
| } |
| |
| public Object addConstant(String name, Object target) |
| { |
| return constants.put(name, target); |
| } |
| |
| public Object removeConstant(String name) |
| { |
| return constants.remove(name); |
| } |
| |
| public void addCommand(String scope, Object target, String function) |
| { |
| addCommand(scope, target, function, 0); |
| } |
| |
| public void addCommand(String scope, Object target, String function, int ranking) |
| { |
| String key = (scope + ":" + function).toLowerCase(); |
| Map<Object, Integer> cmdMap = commands.get(key); |
| if (cmdMap == null) |
| { |
| commands.putIfAbsent(key, new LinkedHashMap<Object, Integer>()); |
| cmdMap = commands.get(key); |
| } |
| cmdMap.put(target, ranking); |
| } |
| |
| public void removeCommand(String scope, String function) |
| { |
| // TODO: WARNING: this method does remove all mapping for scope:function |
| String key = (scope + ":" + function).toLowerCase(); |
| commands.remove(key); |
| } |
| |
| public void removeCommand(String scope, String function, Object target) |
| { |
| // TODO: WARNING: this method does remove all mapping for scope:function |
| String key = (scope + ":" + function).toLowerCase(); |
| Map<Object, Integer> cmdMap = commands.get(key); |
| if (cmdMap != null) |
| { |
| cmdMap.remove(target); |
| } |
| } |
| |
| public void removeCommand(Object target) |
| { |
| for (Map<Object, Integer> cmdMap : commands.values()) |
| { |
| cmdMap.remove(target); |
| } |
| } |
| |
| private String[] getFunctions(Class<?> target) |
| { |
| String[] functions; |
| Set<String> list = new TreeSet<>(); |
| Method methods[] = target.getMethods(); |
| for (Method m : methods) |
| { |
| if (m.getDeclaringClass().equals(Object.class)) |
| { |
| continue; |
| } |
| list.add(m.getName()); |
| if (m.getName().startsWith("get")) |
| { |
| String s = m.getName().substring(3); |
| if (s.length() > 0) |
| { |
| list.add(s.substring(0, 1).toLowerCase() + s.substring(1)); |
| } |
| } |
| } |
| |
| functions = list.toArray(new String[list.size()]); |
| return functions; |
| } |
| |
| public Object convert(CommandSession session, Class<?> desiredType, Object in) |
| { |
| int[] cost = new int[1]; |
| Object ret = Reflective.coerce(session, desiredType, in, cost); |
| if (ret == Reflective.NO_MATCH) |
| { |
| throw new IllegalArgumentException(String.format( |
| "Cannot convert %s(%s) to %s", in, in != null ? in.getClass() : "null", desiredType)); |
| } |
| return ret; |
| } |
| |
| Object doConvert(Class<?> desiredType, Object in) |
| { |
| for (Converter c : converters) |
| { |
| try |
| { |
| Object converted = c.convert(desiredType, in); |
| if (converted != null) |
| { |
| return converted; |
| } |
| } |
| catch (Exception e) |
| { |
| // Ignore |
| e.getCause(); |
| } |
| } |
| |
| return null; |
| } |
| |
| // eval is needed to force expansions to be treated as commands (FELIX-1473) |
| public Object eval(CommandSession session, Object[] argv) throws Exception |
| { |
| StringBuilder buf = new StringBuilder(); |
| |
| for (Object arg : argv) |
| { |
| if (buf.length() > 0) |
| buf.append(' '); |
| buf.append(arg); |
| } |
| |
| return session.execute(buf); |
| } |
| |
| void beforeExecute(CommandSession session, CharSequence commandline) |
| { |
| for (CommandSessionListener l : listeners) |
| { |
| try |
| { |
| l.beforeExecute(session, commandline); |
| } |
| catch (Throwable t) |
| { |
| // Ignore |
| } |
| } |
| } |
| |
| void afterExecute(CommandSession session, CharSequence commandline, Exception exception) |
| { |
| for (CommandSessionListener l : listeners) |
| { |
| try |
| { |
| l.afterExecute(session, commandline, exception); |
| } |
| catch (Throwable t) |
| { |
| // Ignore |
| } |
| } |
| } |
| |
| void afterExecute(CommandSession session, CharSequence commandline, Object result) |
| { |
| for (CommandSessionListener l : listeners) |
| { |
| try |
| { |
| l.afterExecute(session, commandline, result); |
| } |
| catch (Throwable t) |
| { |
| // Ignore |
| } |
| } |
| } |
| |
| public Object expr(CommandSessionImpl session, CharSequence expr) |
| { |
| return new Expression(expr.toString()).eval(session.variables); |
| } |
| } |