blob: 451a1c8f0e240565c18bcf0fcdf43ecc10e17d2b [file] [log] [blame]
/*
* 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);
}
}