View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.model.meta;
17  
18  import org.fourthline.cling.model.ModelUtil;
19  import org.fourthline.cling.model.Validatable;
20  import org.fourthline.cling.model.ValidationError;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.logging.Logger;
25  
26  /**
27   * Describes an action and its input/output arguments.
28   *
29   * @author Christian Bauer
30   */
31  public class Action<S extends Service> implements Validatable {
32  
33      final private static Logger log = Logger.getLogger(Action.class.getName());
34  
35      final private String name;
36      final private ActionArgument[] arguments;
37      final private ActionArgument[] inputArguments;
38      final private ActionArgument[] outputArguments;
39  
40      // Package mutable state
41      private S service;
42  
43      public Action(String name, ActionArgument[] arguments) {
44          this.name = name;
45          if (arguments != null) {
46  
47              List<ActionArgument> inputList= new ArrayList<>();
48              List<ActionArgument> outputList = new ArrayList<>();
49  
50              for (ActionArgument argument : arguments) {
51                  argument.setAction(this);
52                  if (argument.getDirection().equals(ActionArgument.Direction.IN))
53                      inputList.add(argument);
54                  if (argument.getDirection().equals(ActionArgument.Direction.OUT))
55                      outputList.add(argument);
56              }
57  
58              this.arguments = arguments;
59              this.inputArguments = inputList.toArray(new ActionArgument[inputList.size()]);
60              this.outputArguments = outputList.toArray(new ActionArgument[outputList.size()]);
61          } else {
62              this.arguments = new ActionArgument[0];
63              this.inputArguments = new ActionArgument[0];
64              this.outputArguments = new ActionArgument[0];
65          }
66      }
67  
68      public String getName() {
69          return name;
70      }
71  
72      public boolean hasArguments() {
73          return getArguments() != null && getArguments().length > 0;
74      }
75  
76      public ActionArgument[] getArguments() {
77          return arguments;
78      }
79  
80      public S getService() {
81          return service;
82      }
83  
84      void setService(S service) {
85          if (this.service != null)
86              throw new IllegalStateException("Final value has been set already, model is immutable");
87          this.service = service;
88      }
89  
90      public ActionArgument<S> getFirstInputArgument() {
91          if (!hasInputArguments()) throw new IllegalStateException("No input arguments: " + this);
92          return getInputArguments()[0];
93      }
94  
95      public ActionArgument<S> getFirstOutputArgument() {
96          if (!hasOutputArguments()) throw new IllegalStateException("No output arguments: " + this);
97          return getOutputArguments()[0];
98      }
99  
100     public ActionArgument<S>[] getInputArguments() {
101         return inputArguments;
102     }
103 
104     public ActionArgument<S> getInputArgument(String name) {
105         for (ActionArgument<S> arg : getInputArguments()) {
106             if (arg.isNameOrAlias(name)) return arg;
107         }
108         return null;
109     }
110 
111     public ActionArgument<S>[] getOutputArguments() {
112         return outputArguments;
113     }
114 
115     public ActionArgument<S> getOutputArgument(String name) {
116         for (ActionArgument<S> arg : getOutputArguments()) {
117             if (arg.getName().equals(name)) return arg;
118         }
119         return null;
120     }
121 
122     public boolean hasInputArguments() {
123         return getInputArguments() != null && getInputArguments().length > 0;
124     }
125 
126     public boolean hasOutputArguments() {
127         return getOutputArguments() != null && getOutputArguments().length > 0;
128     }
129 
130 
131     @Override
132     public String toString() {
133         return "(" + getClass().getSimpleName() +
134                 ", Arguments: " + (getArguments() != null ? getArguments().length : "NO ARGS") +
135                 ") " + getName();
136     }
137 
138     public List<ValidationError> validate() {
139         List<ValidationError> errors = new ArrayList<>();
140 
141         if (getName() == null || getName().length() == 0) {
142             errors.add(new ValidationError(
143                     getClass(),
144                     "name",
145                     "Action without name of: " + getService()
146             ));
147         } else if (!ModelUtil.isValidUDAName(getName())) {
148             log.warning("UPnP specification violation of: " + getService().getDevice());
149             log.warning("Invalid action name: " + this);
150         }
151 
152         for (ActionArgument actionArgument : getArguments()) {
153             // Check argument relatedStateVariable in service state table
154 
155             if (getService().getStateVariable(actionArgument.getRelatedStateVariableName()) == null) {
156                 errors.add(new ValidationError(
157                         getClass(),
158                         "arguments",
159                         "Action argument references an unknown state variable: " + actionArgument.getRelatedStateVariableName()
160                 ));
161             }
162         }
163 
164         ActionArgument retValueArgument = null;
165         int retValueArgumentIndex = 0;
166         int i = 0;
167         for (ActionArgument actionArgument : getArguments()) {
168             // Check retval
169             if (actionArgument.isReturnValue()) {
170                 if (actionArgument.getDirection() == ActionArgument.Direction.IN) {
171                     log.warning("UPnP specification violation of :" + getService().getDevice());
172                     log.warning("Input argument can not have <retval/>");
173                 } else {
174                     if (retValueArgument != null) {
175                         log.warning("UPnP specification violation of: " + getService().getDevice());
176                         log.warning("Only one argument of action '" + getName() + "' can be <retval/>");
177                     }
178                     retValueArgument = actionArgument;
179                     retValueArgumentIndex = i;
180                 }
181             }
182             i++;
183         }
184         if (retValueArgument != null) {
185             for (int j = 0; j < retValueArgumentIndex; j++) {
186                 ActionArgument a = getArguments()[j];
187                 if (a.getDirection() == ActionArgument.Direction.OUT) {
188                     log.warning("UPnP specification violation of: " + getService().getDevice());
189                     log.warning("Argument '" + retValueArgument.getName() + "' of action '" + getName() + "' is <retval/> but not the first OUT argument");
190                 }
191             }
192         }
193 
194         for (ActionArgument argument : arguments) {
195             errors.addAll(argument.validate());
196         }
197 
198         return errors;
199     }
200 
201     public Action<S> deepCopy() {
202         ActionArgument<S>[] actionArgumentsDupe = new ActionArgument[getArguments().length];
203         for (int i = 0; i < getArguments().length; i++) {
204             ActionArgument arg = getArguments()[i];
205             actionArgumentsDupe[i] = arg.deepCopy();
206         }
207 
208         return new Action<>(
209                 getName(),
210                 actionArgumentsDupe
211         );
212     }
213 
214 }