// 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 streamer; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import streamer.debug.FakeSink; import streamer.debug.FakeSource; public class BaseElement implements Element { protected String id; /** * Constant for @see cap() method to indicate that length is not restricted. */ public static final int UNLIMITED = -1; /** * Set to true to enable debugging messages. */ protected boolean verbose = false; /** * Limit on number of packets sent to sink from this element. Can be handy for * fake elements and handshake-related elements. */ protected int numBuffers = 0; /** * Number of packets sent to sink. */ protected int packetNumber = 0; /** * Recommended size for incoming buffer in pull mode. */ protected int incommingBufLength = -1; protected Map<String, DataSource> inputPads = new HashMap<String, DataSource>(); protected Map<String, DataSink> outputPads = new HashMap<String, DataSink>(); public BaseElement(String id) { this.id = id; verbose = System.getProperty("streamer.Element.debug", "false").equals("true") || System.getProperty("streamer.Element.debug", "").contains(id); } @Override public String toString() { return "Element(" + id + ")"; } @Override public Link getLink(String padName) { if (inputPads.containsKey(padName)) return (Link)inputPads.get(padName); else if (outputPads.containsKey(padName)) return (Link)outputPads.get(padName); else return null; } @Override public Set<String> getPads(Direction direction) { switch (direction) { case IN: return inputPads.keySet(); case OUT: return outputPads.keySet(); } return null; } @Override public void validate() { for (String padName : inputPads.keySet()) { if (inputPads.get(padName) == null) throw new RuntimeException("[ " + this + "] Required input pad is not connected. Pad name: " + padName + "."); } for (String padName : outputPads.keySet()) { if (outputPads.get(padName) == null) throw new RuntimeException("[ " + this + "] Required output pad is not connected. Pad name: " + padName + "."); } } @Override public void setLink(String padName, Link link, Direction direction) { switch (direction) { case IN: if (inputPads.get(padName) != null) throw new RuntimeException("Cannot link more than one wire to same pad. Element: " + this + ", pad: " + padName + ":" + direction + ", new link: " + link + ", existing link: " + inputPads.get(padName) + "."); inputPads.put(padName, link); link.setSink(this); break; case OUT: if (outputPads.get(padName) != null) throw new RuntimeException("Cannot link more than one wire to same pad. Element: " + this + ", pad: " + padName + ":" + direction + ", new link: " + link + ", existing link: " + outputPads.get(padName) + "."); outputPads.put(padName, link); link.setSource(this); break; } } @Override public void dropLink(String padName) { if (inputPads.containsKey(padName)) { Link link = (Link)inputPads.remove(padName); if (link != null) link.setSink(null); } if (outputPads.containsKey(padName)) { Link link = (Link)outputPads.remove(padName); if (link != null) link.setSource(null); } } /** * By default, try to pull data from input links. * * Override this method in data source elements. */ @Override public void poll(boolean block) { // inputPads can be changed in handleData (see switchOff in OneTimeSwitch) // as this results in an undefined response from the iterator on inputPads // use a copy of the map to iterate over. Map<String, DataSource> pads = new HashMap<String, DataSource>(); pads.putAll(inputPads); for (DataSource source : pads.values()) { Link link = (Link)source; ByteBuffer buf = link.pull(block); if (buf != null) { handleData(buf, link); } } } /** * By default, do nothing with incoming data and retransmit data to all output * pads. */ @Override public void handleData(ByteBuffer buf, Link link) { if (verbose) System.out.println("[" + this + "] INFO: Data received: " + buf + "."); pushDataToAllOuts(buf); } /** * Send data to all output pads. */ protected final void pushDataToAllOuts(ByteBuffer buf) { if (buf == null) throw new NullPointerException(); //return; if (outputPads.size() == 0) throw new RuntimeException("Number of outgoing connection is zero. Cannot send data to output. Data: " + buf + "."); // Send data to all pads with OUT direction for (DataSink out : outputPads.values()) { if (verbose) System.out.println("[" + this + "] INFO: Sending buf " + buf + " to " + out + "."); buf.rewindCursor(); buf.ref(); out.sendData(buf); } buf.unref(); packetNumber++; } /** * Send data to given pad only. */ protected void pushDataToPad(String padName, ByteBuffer buf) { if (buf == null) return; if (verbose) System.out.println("[" + this + "] INFO: Sending buf " + buf + " to " + padName + "."); DataSink link = outputPads.get(padName); if (link == null) throw new RuntimeException("Output pad of " + this + " element is not connected to a link. Pad name: " + padName + "."); buf.rewindCursor(); link.sendData(buf); } /** * By default, do nothing with incoming event and retransmit event to all * pads. */ @SuppressWarnings("incomplete-switch") @Override public void handleEvent(Event event, Direction direction) { if (verbose) System.out.println("[" + this + "] INFO: Event " + event + ":" + direction + " is received."); switch (event) { case STREAM_CLOSE: onClose(); break; case STREAM_START: onStart(); break; } sendEventToAllPads(event, direction); } /** * Override this method to do something when STREAM_CLOSE event arrives. */ protected void onClose() { } /** * Override this method to do something when STREAM_START event arrives. */ protected void onStart() { } /** * Send event to all outputs. * * @param event * a event * @param direction * IN to send event to input pads, OUT to send event to all output * pads */ protected final void sendEventToAllPads(Event event, Direction direction) { if (verbose) System.out.println("[" + this + "] INFO: Sending event " + event + ":" + direction + "."); switch (direction) { case IN: // Send event to all pads with IN direction for (DataSource in : inputPads.values()) { in.sendEvent(event, direction); } break; case OUT: // Send event to all pads with OUT direction for (DataSink out : outputPads.values()) { out.sendEvent(event, direction); } break; } } /** * Ensure that packet has required minimum and maximum length, cuts tail when * necessary. * * @param buf * a buffer * @param minLength * minimum length of packet or -1 * @param maxLength * maximum length of packet or -1 * @param link * source link, to push unnecessary data back * @param fromCursor * if true, then position will be included into calculation * @return true, if buffer is long enough, false otherwise */ public boolean cap(ByteBuffer buf, int minLength, int maxLength, Link link, boolean fromCursor) { if (buf == null) return false; int length = buf.length; int cursor; if (fromCursor) cursor = buf.cursor; else cursor = 0; length -= cursor; if ((minLength < 0 || length >= minLength) && (maxLength < 0 || length <= maxLength)) // Buffer is already in bounds return true; // Buffer is too small, wait for the rest of buffer if (minLength >= 0 && length < minLength) { if (verbose) System.out.println("[" + this + "] INFO: Buffer is too small. Min length: " + minLength + ", data length (after cursor): " + length + "."); link.pushBack(buf.slice(0, length + cursor, true), minLength + cursor); return false; } else if (maxLength >= 0 && length > maxLength) { if (verbose) System.out.println("[" + this + "] INFO: Buffer is too big. Max length: " + maxLength + ", data length (after cursor): " + length + "."); // Buffer is too big, cut unnecessary tail link.pushBack(buf.slice(maxLength + cursor, length - maxLength, true)); buf.length = maxLength + cursor; } return true; } @Override public void dropLink(Link link) { if (inputPads.containsValue(link)) { for (Entry<String, DataSource> entry : inputPads.entrySet()) if (link.equals(entry.getValue())) { inputPads.remove(entry.getKey()); break; } } if (outputPads.containsValue(link)) { for (Entry<String, DataSink> entry : outputPads.entrySet()) if (link.equals(entry.getValue())) { outputPads.remove(entry.getKey()); break; } } } @Override public void replaceLink(Link existingLink, Link newLink) { if (inputPads.containsValue(existingLink)) { for (Entry<String, DataSource> entry : inputPads.entrySet()) if (existingLink.equals(entry.getValue())) { inputPads.put(entry.getKey(), newLink); newLink.setSink(this); break; } } if (outputPads.containsValue(existingLink)) { for (Entry<String, DataSink> entry : outputPads.entrySet()) if (existingLink.equals(entry.getValue())) { outputPads.put(entry.getKey(), newLink); newLink.setSource(this); break; } } } @Override public String getId() { return id; } /** * Example. */ public static void main(String args[]) { Element source = new FakeSource("source") { { verbose = true; numBuffers = 10; incommingBufLength = 3; delay = 100; } }; Element sink = new FakeSink("sink") { { verbose = true; } }; Pipeline pipeline = new PipelineImpl("test"); pipeline.add(source); pipeline.add(new BaseElement("t1")); pipeline.add(new BaseElement("t2")); pipeline.add(new BaseElement("t3")); pipeline.add(new BaseElement("t4")); pipeline.add(sink); pipeline.link("source", "t1", "t2", "t3", "t4", "sink"); // Links between source-t1-t2 will operate in pull mode. // Links between t3-t4-sink will operate in push mode. // Link between t2-t3 will run main loop (pull from source and push to // sink). Link link = pipeline.getLink("t3", STDOUT); link.sendEvent(Event.STREAM_START, Direction.IN); link.run(); } }