@Override
  @Deprecated
  public ContentResult fetchOrderContent(OrderItem orderItem) throws IOException {
    String downloadUri = Objects.requireNonNull(
        Objects.requireNonNull(orderItem, "Missing order item").getUri(LINK_DOWNLOAD), "Missing download URI");
    String fetchUri = restClient.uri(downloadUri)
        .addParameter("downloadToken", "")
        .build();
    return restClient.get(fetchUri, contentResultFactory);
  }

@
Override
@
Deprecated
public
ContentResult
fetchOrderContent
(
OrderItem
orderItem
)
throws
IOException
{
String
downloadUri
=
Objects
.
requireNonNull
(
Objects
.
requireNonNull
(
orderItem
,
"Missing order item"
)
.
getUri
(
LINK_DOWNLOAD
)
,
"Missing download URI"
)
;
String
fetchUri
=
restClient
.
uri
(
downloadUri
)
.
addParameter
(
"downloadToken"
,
""
)
.
build
(
)
;
return
restClient
.
get
(
fetchUri
,
contentResultFactory
)
;
}

Fetch the content for the specified order item.
@param orderItem The order item.
@return A ContentResult
@throws IOException When an I/O error occurs
@deprecated Will be removed without replacement in a future version

Fetch the content for the specified order item .


@Override
  @Deprecated
  public LinkContainer uploadTransformation(ExportTransformation exportTransformation, InputStream zip)
      throws IOException {
    String uri = exportTransformation.getUri(LINK_EXPORT_TRANSFORMATION_ZIP);
    return restClient.post(uri, LinkContainer.class, new BinaryPart("file", zip, "stylesheet.zip"));
  }

@ Override @ Deprecated public LinkContainer uploadTransformation ( ExportTransformation exportTransformation , InputStream zip ) throws IOException { String uri = exportTransformation . getUri ( LINK_EXPORT_TRANSFORMATION_ZIP ) ; return restClient . post ( uri , LinkContainer . class , new BinaryPart ( "file" , zip , "stylesheet.zip" ) ) ; }

Upload the transformation zip with the stylesheet into Archive.
@param exportTransformation The export transformation.
@param zip The input stream of zip with stylesheet.
@throws IOException When an I/O error occurs
@return The uploaded transformation
@deprecated Use declarative configuration to define transformations

Upload the transformation zip with the stylesheet into Archive .


public FileGenerationMetrics generate(Iterator<C> components) throws IOException {
    File result = fileSupplier.get();
    return new FileGenerationMetrics(result, generate(components, new FileBuffer(result)));
  }

public FileGenerationMetrics generate ( Iterator < C > components ) throws IOException { File result = fileSupplier . get ( ) ; return new FileGenerationMetrics ( result , generate ( components , new FileBuffer ( result ) ) ) ; }

Generate a file by assembling components.
@param components The components to assemble
@return The generated file and metrics about the generation process
@throws IOException When an I/O error occurs

Generate a file by assembling components .


public static File in(File parentDir) {
    File result = new File(parentDir, UUID.randomUUID()
      .toString());
    if (!result.mkdirs()) {
      throw new RuntimeIoException(new IOException("Could not create directory: " + result));
    }
    return result;
  }

public
static
File
in
(
File
parentDir
)
{
File
result
=
new
File
(
parentDir
,
UUID
.
randomUUID
(
)
.
toString
(
)
)
;
if
(
!
result
.
mkdirs
(
)
)
{
throw
new
RuntimeIoException
(
new
IOException
(
"Could not create directory: "
+
result
)
)
;
}
return
result
;
}

Create a new directory in the given parent directory.
@param parentDir The parent directory in which to create a new directory
@return The newly created directory

Create a new directory in the given parent directory .


public P end() {
    parent.addChildObject(English.plural(object.getType()), object);
    return parent;
  }

public P end ( ) { parent . addChildObject ( English . plural ( object . getType ( ) ) , object ) ; return parent ; }

End this builder.
@return The parent builder


protected <S> void registerAdaptor(STGroup group, Class<S> type, ModelAdaptor adaptor) {
    group.registerModelAdaptor(type, adaptor);
  }

protected < S > void registerAdaptor ( STGroup group , Class < S > type , ModelAdaptor adaptor ) { group . registerModelAdaptor ( type , adaptor ) ; }

Registers a ModelAdaptor with the group. Override this method if you want to suppress one OOTB adaptor but not all.
@param <S> The domain object type
@param group The template group
@param type The domain object class
@param adaptor The adaptor that will be used to extract properties of objects of type S

Registers a ModelAdaptor with the group . Override this method if you want to suppress one OOTB adaptor but not all .


protected <S> void registerRenderer(STGroup group, Class<S> type, AttributeRenderer attributeRenderer) {
    group.registerRenderer(type, attributeRenderer);
  }

protected < S > void registerRenderer ( STGroup group , Class < S > type , AttributeRenderer attributeRenderer ) { group . registerRenderer ( type , attributeRenderer ) ; }

Registers a renderer with the group. Override this method if you want to suppress one OOTB renderer but not all.
@param <S> The domain object type
@param group The template group
@param type The domain object class
@param attributeRenderer The renderer that will be used to render objects of type S into a String

Registers a renderer with the group . Override this method if you want to suppress one OOTB renderer but not all .


protected ST prepareTemplate(ST prototype, D domainObject, Map<String, ContentInfo> contentInfo) {
    ST template = new ST(prototype);
    template.add(MODEL_VARIABLE, domainObject);
    template.add(CONTENT_VARIABLE, contentInfo);
    return template;
  }

protected ST prepareTemplate ( ST prototype , D domainObject , Map < String , ContentInfo > contentInfo ) { ST template = new ST ( prototype ) ; template . add ( MODEL_VARIABLE , domainObject ) ; template . add ( CONTENT_VARIABLE , contentInfo ) ; return template ; }

Prepares the template by adding the variables.
@param prototype The template prototype
@param domainObject The domain object
@param contentInfo The reference information and the encoded content hashes
@return The prepared template instance

Prepares the template by adding the variables .


public synchronized void add(D domainObject) throws IOException {
    if (shouldStartNewSip(domainObject)) {
      startSip();
    }
    assembler.add(domainObject);
  }

public synchronized void add ( D domainObject ) throws IOException { if ( shouldStartNewSip ( domainObject ) ) { startSip ( ) ; } assembler . add ( domainObject ) ; }

Add a domain object to the batch of SIPs.
@param domainObject The domain object to add
@throws IOException When an I/O error occurs

Add a domain object to the batch of SIPs .


public void setProperty(String name, Object value) {
    properties.put(name, toJsonValue(value));
  }

public void setProperty ( String name , Object value ) { properties . put ( name , toJsonValue ( value ) ) ; }

Set the value of a property.
@param name The name of the property to set
@param value The value to set the property to


public void addChildObject(String collection, ConfigurationObject childObject) {
    childObjects.computeIfAbsent(collection, ignored -> new ArrayList<>()).add(childObject);
  }

public void addChildObject ( String collection , ConfigurationObject childObject ) { childObjects . computeIfAbsent ( collection , ignored -> new ArrayList <> ( ) ) . add ( childObject ) ; }

Add an object that is to be owned by this object.
@param collection The collection to store the child object under
@param childObject The child object to add

Add an object that is to be owned by this object .


public static Supplier<File> fromDirectory(File dir, String prefix, String suffix) {
    return new Supplier<File>() {
      private int count;

      @Override
      public File get() {
        return new File(ensureDir(dir), String.format("%s%d%s", prefix, ++count, suffix));
      }
    };
  }

public static Supplier < File > fromDirectory ( File dir , String prefix , String suffix ) { return new Supplier < File > ( ) { private int count ; @ Override public File get ( ) { return new File ( ensureDir ( dir ) , String . format ( "%s%d%s" , prefix , ++ count , suffix ) ) ; } } ; }

Returns a supplier that creates sequentially named files in the given directory.
@param dir The directory in which to create files
@param prefix The prefix for the file names
@param suffix The suffix for the file names
@return A supplier that creates sequentially named files in the given directory

Returns a supplier that creates sequentially named files in the given directory .


public static void copy(InputStream in, OutputStream out, int bufferSize, HashAssembler hashAssembler)
      throws IOException {
    byte[] buffer = new byte[bufferSize];
    int numRead = Objects.requireNonNull(in, "Missing input").read(buffer);
    if (numRead == 0) {
      throw new IllegalArgumentException("Missing content");
    }
    Objects.requireNonNull(out, "Missing output");
    while (numRead > 0) {
      out.write(buffer, 0, numRead);
      hashAssembler.add(buffer, numRead);
      numRead = in.read(buffer);
    }
  }

public
static
void
copy
(
InputStream
in
,
OutputStream
out
,
int
bufferSize
,
HashAssembler
hashAssembler
)
throws
IOException
{
byte
[
]
buffer
=
new
byte
[
bufferSize
]
;
int
numRead
=
Objects
.
requireNonNull
(
in
,
"Missing input"
)
.
read
(
buffer
)
;
if
(
numRead
==
0
)
{
throw
new
IllegalArgumentException
(
"Missing content"
)
;
}
Objects
.
requireNonNull
(
out
,
"Missing output"
)
;
while
(
numRead
>
0
)
{
out
.
write
(
buffer
,
0
,
numRead
)
;
hashAssembler
.
add
(
buffer
,
numRead
)
;
numRead
=
in
.
read
(
buffer
)
;
}
}

Utility method to copy the bytes from an InputStream to an OutputStream while also assembling a hash value in the
process.
@param in The source stream
@param out The target stream
@param bufferSize The size of the internal buffer
@param hashAssembler The HashAssembler to use.
@throws IOException if an error occurs when copying the streams

Utility method to copy the bytes from an InputStream to an OutputStream while also assembling a hash value in the process .


public static Document parse(File file) {
    if (!file.isFile()) {
      throw new IllegalArgumentException("Missing file: " + file.getAbsolutePath());
    }
    try {
      try (InputStream stream = Files.newInputStream(file.toPath(), StandardOpenOption.READ)) {
        return parse(stream);
      }
    } catch (IOException e) {
      throw new IllegalArgumentException("Failed to parse " + file.getAbsolutePath(), e);
    }
  }

public
static
Document
parse
(
File
file
)
{
if
(
!
file
.
isFile
(
)
)
{
throw
new
IllegalArgumentException
(
"Missing file: "
+
file
.
getAbsolutePath
(
)
)
;
}
try
{
try
(
InputStream
stream
=
Files
.
newInputStream
(
file
.
toPath
(
)
,
StandardOpenOption
.
READ
)
)
{
return
parse
(
stream
)
;
}
}
catch
(
IOException
e
)
{
throw
new
IllegalArgumentException
(
"Failed to parse "
+
file
.
getAbsolutePath
(
)
,
e
)
;
}
}

Parse the content of a given file into an XML document.
@param file The file to parse
@return The parsed XML document

Parse the content of a given file into an XML document .


public static Document parse(Reader reader) {
    DocumentBuilder documentBuilder = getDocumentBuilder();
    try {
      return documentBuilder.parse(new InputSource(reader));
    } catch (SAXException | IOException e) {
      throw new IllegalArgumentException("Failed to parse XML document", e);
    } finally {
      documentBuilder.reset();
    }
  }

public
static
Document
parse
(
Reader
reader
)
{
DocumentBuilder
documentBuilder
=
getDocumentBuilder
(
)
;
try
{
return
documentBuilder
.
parse
(
new
InputSource
(
reader
)
)
;
}
catch
(
SAXException
|
IOException
e
)
{
throw
new
IllegalArgumentException
(
"Failed to parse XML document"
,
e
)
;
}
finally
{
documentBuilder
.
reset
(
)
;
}
}

Parse the content of a given reader into an XML document.
@param reader The reader to parse
@return The parsed XML document

Parse the content of a given reader into an XML document .


public static Stream<Element> elementsIn(Element parent) {
    return nodesIn(parent).filter(n -> n.getNodeType() == Node.ELEMENT_NODE)
      .map(n -> (Element)n);
  }

public static Stream < Element > elementsIn ( Element parent ) { return nodesIn ( parent ) . filter ( n -> n . getNodeType ( ) == Node . ELEMENT_NODE ) . map ( n -> ( Element ) n ) ; }

Return the elements under a given parent element.
@param parent The parent of the elements to return
@return The children of the parent element

Return the elements under a given parent element .


public static Stream<Node> nodesIn(Element parent) {
    return StreamSupport.stream(new ChildNodesSpliterator(parent), false);
  }

public static Stream < Node > nodesIn ( Element parent ) { return StreamSupport . stream ( new ChildNodesSpliterator ( parent ) , false ) ; }

Return the nodes under a given parent element.
@param parent The parent of the nodes to return
@return The children of the parent element

Return the nodes under a given parent element .


public static Element getFirstChildElement(Element parent, String... childNames) {
    return firstOf(namedElementsIn(parent, childNames));
  }

public static Element getFirstChildElement ( Element parent , String ... childNames ) { return firstOf ( namedElementsIn ( parent , childNames ) ) ; }

Return the first child element of a given parent element whose tag matches any of the given names. When no child
names are given, the first child element is returned.
@param parent The parent of the element to return
@param childNames The acceptable names of the children
@return The first child element that matches any of the names

Return the first child element of a given parent element whose tag matches any of the given names . When no child names are given the first child element is returned .


public static Stream<Element> namedElementsIn(Element parent, String... childNames) {
    return elementsIn(parent).filter(e -> isName(e, childNames));
  }

public static Stream < Element > namedElementsIn ( Element parent , String ... childNames ) { return elementsIn ( parent ) . filter ( e -> isName ( e , childNames ) ) ; }

Return the elements under a given parent element whose tag matches any of the given names. When no child
names are given, all child elements are returned.
@param parent The parent of the elements to return
@param childNames The acceptable names of the children
@return The children of the parent element

Return the elements under a given parent element whose tag matches any of the given names . When no child names are given all child elements are returned .


public static void validate(InputStream xml, InputStream xmlSchema, String humanFriendlyDocumentType)
      throws IOException {
    try {
      newXmlSchemaValidator(xmlSchema).validate(new StreamSource(Objects.requireNonNull(xml)));
    } catch (SAXException e) {
      throw new ValidationException("Invalid " + humanFriendlyDocumentType, e);
    }
  }

public
static
void
validate
(
InputStream
xml
,
InputStream
xmlSchema
,
String
humanFriendlyDocumentType
)
throws
IOException
{
try
{
newXmlSchemaValidator
(
xmlSchema
)
.
validate
(
new
StreamSource
(
Objects
.
requireNonNull
(
xml
)
)
)
;
}
catch
(
SAXException
e
)
{
throw
new
ValidationException
(
"Invalid "
+
humanFriendlyDocumentType
,
e
)
;
}
}

Validate an XML document against an XML Schema document.
@param xml The XML document to validate
@param xmlSchema The XML Schema document to validate against
@param humanFriendlyDocumentType A human-friendly name that describes the schema
@throws IOException When an I/O error occurs

Validate an XML document against an XML Schema document .


public static void main(String[] args) {
    try {
      Arguments arguments = new Arguments(args);
      File root = new File(arguments.next("content"));
      if (!root.isDirectory()) {
        root = new File(".");
      }
      String rootPath = root.getCanonicalPath();
      String sip = arguments.next("build/files.zip");
      new FileArchiver().run(rootPath, sip);
    } catch (IOException e) {
      e.printStackTrace(System.out);
      System.exit(1);
    }
  }

public static void main ( String [ ] args ) { try { Arguments arguments = new Arguments ( args ) ; File root = new File ( arguments . next ( "content" ) ) ; if ( ! root . isDirectory ( ) ) { root = new File ( "." ) ; } String rootPath = root . getCanonicalPath ( ) ; String sip = arguments . next ( "build/files.zip" ) ; new FileArchiver ( ) . run ( rootPath , sip ) ; } catch ( IOException e ) { e . printStackTrace ( System . out ) ; System . exit ( 1 ) ; } }

Build a SIP archive from all files in a given directory tree.
@param args The command line args:

The path to the directory to be archived. The default value is the current directory.

The path to the SIP archive to be built. The default value is build/files.zip

Build a SIP archive from all files in a given directory tree .


public ContentBuilder<P> as(InputStream content) {
    try {
      return as(IOUtils.toString(content, StandardCharsets.UTF_8));
    } catch (IOException e) {
      throw new IllegalArgumentException("Failed to read content", e);
    }
  }

public
ContentBuilder
<
P
>
as
(
InputStream
content
)
{
try
{
return
as
(
IOUtils
.
toString
(
content
,
StandardCharsets
.
UTF_8
)
)
;
}
catch
(
IOException
e
)
{
throw
new
IllegalArgumentException
(
"Failed to read content"
,
e
)
;
}
}

Sets the content.
@param content the content to add
@return this builder


public ContentBuilder<P> fromResource(String name) {
    try (InputStream content = ContentBuilder.class.getResourceAsStream(name)) {
      return as(content);
    } catch (IOException e) {
      throw new IllegalArgumentException("Failed to read resource: " + name, e);
    }
  }

public
ContentBuilder
<
P
>
fromResource
(
String
name
)
{
try
(
InputStream
content
=
ContentBuilder
.
class
.
getResourceAsStream
(
name
)
)
{
return
as
(
content
)
;
}
catch
(
IOException
e
)
{
throw
new
IllegalArgumentException
(
"Failed to read resource: "
+
name
,
e
)
;
}
}

Sets the content from a named resource.
@param name the name of the content resource to add
@return this builder

Sets the content from a named resource .


public <T> T andProcessEntry(String entry, Function<InputStream, T> processor) {
    try (ZipFile zipFile = new ZipFile(zip)) {
      return processEntry(zipFile, entry, processor);
    } catch (IOException e) {
      throw new RuntimeIoException(e);
    }
  }

public < T > T andProcessEntry ( String entry , Function < InputStream , T > processor ) { try ( ZipFile zipFile = new ZipFile ( zip ) ) { return processEntry ( zipFile , entry , processor ) ; } catch ( IOException e ) { throw new RuntimeIoException ( e ) ; } }

Process a named entry in the ZIP file.
@param <T> The type of result returned by the processor
@param entry The name of the entry to process
@param processor The processor that handles the entry's content
@return The result of processing the entry


public static String asciiCharactersEncoding(String str) throws QSException {
        if (QSStringUtil.isEmpty(str)) {
            return "";
        }
        try {
            String encoded = URLEncoder.encode(str, QSConstant.ENCODING_UTF8);
            encoded = encoded.replace("%2F", "/");
            encoded = encoded.replace("%3D", "=");
            encoded = encoded.replace("+", "%20");
            encoded = encoded.replace("%3A", ":");
            return encoded;
        } catch (UnsupportedEncodingException e) {
            throw new QSException("UnsupportedEncodingException:", e);
        }
    }

public static String asciiCharactersEncoding ( String str ) throws QSException { if ( QSStringUtil . isEmpty ( str ) ) { return "" ; } try { String encoded = URLEncoder . encode ( str , QSConstant . ENCODING_UTF8 ) ; encoded = encoded . replace ( "%2F" , "/" ) ; encoded = encoded . replace ( "%3D" , "=" ) ; encoded = encoded . replace ( "+" , "%20" ) ; encoded = encoded . replace ( "%3A" , ":" ) ; return encoded ; } catch ( UnsupportedEncodingException e ) { throw new QSException ( "UnsupportedEncodingException:" , e ) ; } }

Chinese characters transform

@param str unEncoded chars
@return encoded chars
@throws QSException UnsupportedEncodingException


public void setSignature(String accessKey, String signature, String gmtTime) throws QSException {
        builder.setHeader(QSConstant.HEADER_PARAM_KEY_DATE, gmtTime);
        setSignature(accessKey, signature);
    }

public void setSignature ( String accessKey , String signature , String gmtTime ) throws QSException { builder . setHeader ( QSConstant . HEADER_PARAM_KEY_DATE , gmtTime ) ; setSignature ( accessKey , signature ) ; }

Set signature and server time.
@param accessKey accessKey
@param signature signature
@param gmtTime time format with GMT
@throws QSException exception


private static int removeWhiteSpace(char[] data) {
        if (data == null) {
            return 0;
        }

        // count characters that's not whitespace
        int newSize = 0;
        int len = data.length;
        for (int i = 0; i < len; i++) {
            if (!isWhiteSpace(data[i])) {
                data[newSize++] = data[i];
            }
        }
        return newSize;
    }

remove WhiteSpace from MIME containing encoded Base64 data.

@param data the byte array of base64 data (with WS)
@return the new length

remove WhiteSpace from MIME containing encoded Base64 data .


private void sign(RequestHandler requestHandler) throws QSException {
        if (callBack != null) {
            String signed = callBack.onSignature(requestHandler.getStringToSignature());
            if (!QSStringUtil.isEmpty(signed))
                requestHandler.setSignature(callBack.onAccessKey(), signed);
            String correctTime = callBack.onCorrectTime(requestHandler.getStringToSignature());
            if (correctTime != null && correctTime.trim().length() > 0)
                requestHandler.getBuilder().setHeader(QSConstant.HEADER_PARAM_KEY_DATE, correctTime);
        }
    }

private void sign ( RequestHandler requestHandler ) throws QSException { if ( callBack != null ) { String signed = callBack . onSignature ( requestHandler . getStringToSignature ( ) ) ; if ( ! QSStringUtil . isEmpty ( signed ) ) requestHandler . setSignature ( callBack . onAccessKey ( ) , signed ) ; String correctTime = callBack . onCorrectTime ( requestHandler . getStringToSignature ( ) ) ; if ( correctTime != null && correctTime . trim ( ) . length ( ) > 0 ) requestHandler . getBuilder ( ) . setHeader ( QSConstant . HEADER_PARAM_KEY_DATE , correctTime ) ; } }

When sending a request will call this method to sign with server.
@param requestHandler handler of the request.
@throws QSException exception

When sending a request will call this method to sign with server .


private void setData(String objectKey, Recorder recorder) {
        if (recorder == null) return;
        String upload = new Gson().toJson(uploadModel);
        recorder.set(objectKey, upload.getBytes());
    }

private void setData ( String objectKey , Recorder recorder ) { if ( recorder == null ) return ; String upload = new Gson ( ) . toJson ( uploadModel ) ; recorder . set ( objectKey , upload . getBytes ( ) ) ; }

Set data in the upload with a recorder.
@param objectKey key
@param recorder recorder

Set data in the upload with a recorder .


private void completeMultiUpload(String objectKey, String fileName, String eTag, String uploadID, long length) throws QSException {
        CompleteMultipartUploadInput completeMultipartUploadInput =
                new CompleteMultipartUploadInput(uploadID, partCounts, 0);

        completeMultipartUploadInput.setContentLength(length);

        // Set content disposition to the object.
        if (!QSStringUtil.isEmpty(fileName)) {
            try {
                String keyName = QSStringUtil.percentEncode(fileName, "UTF-8");
                completeMultipartUploadInput.setContentDisposition(String.format(
                        "attachment; filename=\"%s\"; filename*=utf-8''%s", keyName, keyName));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }

        // Set the Md5 info to the object.
        if (!QSStringUtil.isEmpty(eTag)) {
            completeMultipartUploadInput.setETag(eTag);
        }

        RequestHandler requestHandler =
                bucket.completeMultipartUploadRequest(objectKey, completeMultipartUploadInput);

        sign(requestHandler);

        Bucket.CompleteMultipartUploadOutput send =
                (Bucket.CompleteMultipartUploadOutput) requestHandler.send();

        if (send.getStatueCode() == 200 || send.getStatueCode() == 201) {
            uploadModel.setUploadComplete(true);
            setData(objectKey, recorder);
        }

        // Response callback.
        if (callBack != null)
            callBack.onAPIResponse(objectKey, send);
    }

Complete the multi upload.
@param objectKey key of the object in the bucket
@param fileName file name of response(content-disposition) when downloaded
@param eTag MD5 info
@param uploadID multi upload id
@param length length of the file
@throws QSException exception


@Override
    public void invoke(final ActionRequest req, final ActionResponse res) throws IOException {
        final NotificationEntry entry = getTarget();
        final String notificationId = entry.getId();
        final Set<String> favoriteNotices = this.getFavoriteNotices(req);
        if (favoriteNotices.contains(notificationId)) {
            favoriteNotices.remove(notificationId);
        } else {
            favoriteNotices.add(notificationId);
        }
        setFavoriteNotices(req, favoriteNotices);
    }

@ Override public void invoke ( final ActionRequest req , final ActionResponse res ) throws IOException { final NotificationEntry entry = getTarget ( ) ; final String notificationId = entry . getId ( ) ; final Set < String > favoriteNotices = this . getFavoriteNotices ( req ) ; if ( favoriteNotices . contains ( notificationId ) ) { favoriteNotices . remove ( notificationId ) ; } else { favoriteNotices . add ( notificationId ) ; } setFavoriteNotices ( req , favoriteNotices ) ; }


public void addEntryState(PortletRequest req, String entryId, NotificationState state) {
        if (usernameFinder.isAuthenticated(req)) {

            final String username = usernameFinder.findUsername(req);

            String idStr = entryId.replaceAll(ID_PREFIX, ""); // remove the prefix

            JpaEntry jpaEntry = notificationDao.getEntry(Long.parseLong(idStr));
            if (jpaEntry != null) {
                JpaEvent event = new JpaEvent();
                event.setEntry(jpaEntry);
                event.setState(state);
                event.setTimestamp(new Timestamp(new Date().getTime()));
                event.setUsername(username);

                notificationDao.createOrUpdateEvent(event);
            }
            else {
                throw new IllegalArgumentException("JpaEntry not found");
            }
        }
    }

public
void
addEntryState
(
PortletRequest
req
,
String
entryId
,
NotificationState
state
)
{
if
(
usernameFinder
.
isAuthenticated
(
req
)
)
{
final
String
username
=
usernameFinder
.
findUsername
(
req
)
;
String
idStr
=
entryId
.
replaceAll
(
ID_PREFIX
,
""
)
;
// remove the prefix
JpaEntry
jpaEntry
=
notificationDao
.
getEntry
(
Long
.
parseLong
(
idStr
)
)
;
if
(
jpaEntry
!=
)
{
JpaEvent
event
=
new
JpaEvent
(
)
;
event
.
setEntry
(
jpaEntry
)
;
event
.
setState
(
state
)
;
event
.
setTimestamp
(
new
Timestamp
(
new
Date
(
)
.
getTime
(
)
)
)
;
event
.
setUsername
(
username
)
;
notificationDao
.
createOrUpdateEvent
(
event
)
;
}
else
{
throw
new
IllegalArgumentException
(
"JpaEntry not found"
)
;
}
}
}

Caller must insure that the state being set has not already been added to the entry
to avoid multiple events with the same state.

Caller must insure that the state being set has not already been added to the entry to avoid multiple events with the same state .


public boolean hasExpired() {
        long now = System.currentTimeMillis();
        if (created + (expiresIn * 1000) + TIMEOUT_BUFFER > now) {
            return false;
        }

        return true;
    }

public boolean hasExpired ( ) { long now = System . currentTimeMillis ( ) ; if ( created + ( expiresIn * 1000 ) + TIMEOUT_BUFFER > now ) { return false ; } return true ; }

Helper method to determine if an SSPToken has expired.

Helper method to determine if an SSPToken has expired .


@RequestMapping(value = "/{notificationId}", method = RequestMethod.GET)
    @ResponseBody
    public EntryDTO getNotification(HttpServletResponse response, @PathVariable("notificationId") long id,
            @RequestParam(value = "full", required = false, defaultValue = "false") boolean full) {
        EntryDTO notification = restService.getNotification(id, full);
        if (notification == null) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
            return null;
        }

        return notification;
    }

@ RequestMapping ( value = "/{notificationId}" , method = RequestMethod . GET ) @ ResponseBody public EntryDTO getNotification ( HttpServletResponse response , @ PathVariable ( "notificationId" ) long id , @ RequestParam ( value = "full" , required = false , defaultValue = "false" ) boolean full ) { EntryDTO notification = restService . getNotification ( id , full ) ; if ( notification == null ) { response . setStatus ( HttpStatus . NOT_FOUND . value ( ) ) ; return null ; } return notification ; }

Get 1 notification by id.

@param response the Http response
@param id the id of the notification
@param full optionally fetch addressee info.  If false, addressee info will be omitted.
@return the notification


@RequestMapping(value = "/{notificationId}/addressees", method = RequestMethod.GET)
    @ResponseBody
    public Set<AddresseeDTO> getAddressees(@PathVariable("notificationId") long id) {
        return restService.getAddressees(id);
    }

@ RequestMapping ( value = "/{notificationId}/addressees" , method = RequestMethod . GET ) @ ResponseBody public Set < AddresseeDTO > getAddressees ( @ PathVariable ( "notificationId" ) long id ) { return restService . getAddressees ( id ) ; }

Get the set of addressees for a notification.

@param id the notification id
@return the set of addressees

Get the set of addressees for a notification .


@RequestMapping(value = "/{notificationId}/addressees/{addresseeId}", method = RequestMethod.GET)
    @ResponseBody
    public AddresseeDTO getAddressee(HttpServletResponse resp,
            @PathVariable("notificationId") long notificationId,
            @PathVariable("addresseeId") long addresseeId) {
        AddresseeDTO dto = restService.getAddressee(addresseeId);
        if (dto == null) {
            resp.setStatus(HttpStatus.NOT_FOUND.value());
            return null;
        }

        return dto;
    }

Get a specific addressee

@param resp the Http Response
@param notificationId the notification id
@param addresseeId the addressee id
@return the matching addressee if available


@RequestMapping(value = "/{notificationId}/events", method = RequestMethod.GET)
    @ResponseBody
    public List<EventDTO> getEventsByNotification(@PathVariable("notificationId") long id) {
        return restService.getEventsByNotification(id);
    }

@ RequestMapping ( value = "/{notificationId}/events" , method = RequestMethod . GET ) @ ResponseBody public List < EventDTO > getEventsByNotification ( @ PathVariable ( "notificationId" ) long id ) { return restService . getEventsByNotification ( id ) ; }

Get the list of events for a notification.

@param id the notification id
@return the list of events

Get the list of events for a notification .


@RequestMapping(value = "/{notificationId}/events/{eventId}", method = RequestMethod.GET)
    @ResponseBody
    public EventDTO getEvent(HttpServletResponse response, @PathVariable("notificationId") long notificationId,
            @PathVariable("eventId") long eventId) {
        EventDTO event = restService.getEvent(eventId);
        if (event == null) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
            return null;
        }

        return event;
    }

@ RequestMapping ( value = "/{notificationId}/events/{eventId}" , method = RequestMethod . GET ) @ ResponseBody public EventDTO getEvent ( HttpServletResponse response , @ PathVariable ( "notificationId" ) long notificationId , @ PathVariable ( "eventId" ) long eventId ) { EventDTO event = restService . getEvent ( eventId ) ; if ( event == null ) { response . setStatus ( HttpStatus . NOT_FOUND . value ( ) ) ; return null ; } return event ; }

Get a specific event.

@param response The Http response
@param notificationId the notification id
@param eventId the event id
@return the matching event, if found


private String getSingleNotificationRESTUrl(HttpServletRequest request, long id) {
        String path = request.getContextPath() + REQUEST_ROOT + id;
        try {
            URL url = new URL(request.getScheme(), request.getServerName(), request.getServerPort(), path);
            return url.toExternalForm();

        } catch (MalformedURLException e) {
            // if it fails, just return a relative path.  Not ideal, but better than nothing...
            log.warn("Error building Location header", e);
            return path;
        }
    }

Build the URL for a specific notification.

@param request the Http request
@param id the notification id
@return the URL to hit that specific id

Build the URL for a specific notification .


@Override
    @Transactional(readOnly=true)
    public JpaEntry getEntry(long entryId) {
        Validate.isTrue(entryId > 0, "Invalid entryId:  " + entryId);

        JpaEntry rslt = entityManager.find(JpaEntry.class, entryId);
        return rslt;
    }

@
Override
@
Transactional
(
readOnly
=
true
)
public
JpaEntry
getEntry
(
long
entryId
)
{
Validate
.
isTrue
(
entryId
>
0
,
"Invalid entryId:  "
+
entryId
)
;
JpaEntry
rslt
=
entityManager
.
find
(
JpaEntry
.
class
,
entryId
)
;
return
rslt
;
}

Search for a JpaEntry with the specified Id. If the entry exists in the
persistence context, it is returned;  otherwise, null is returned.

Search for a JpaEntry with the specified Id . If the entry exists in the persistence context it is returned ; otherwise null is returned .


private synchronized SSPToken getAuthenticationToken(boolean forceUpdate) throws MalformedURLException, RestClientException {
        if (authenticationToken != null && !authenticationToken.hasExpired() && !forceUpdate) {
            return authenticationToken;
        }

        String authString = getClientId() + ":" + getClientSecret();
        String authentication = new Base64().encodeToString(authString.getBytes());

        HttpHeaders headers = new HttpHeaders();
        headers.add(AUTHORIZATION, BASIC + " " + authentication);

        // form encode the grant_type...
        MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
        form.add(GRANT_TYPE, CLIENT_CREDENTIALS);

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(form, headers);

        URL authURL = getAuthenticationURL();
        authenticationToken = restTemplate.postForObject(authURL.toExternalForm(), request, SSPToken.class);
        return authenticationToken;
    }

private
synchronized
SSPToken
getAuthenticationToken
(
boolean
forceUpdate
)
throws
MalformedURLException
,
RestClientException
{
if
(
authenticationToken
!=
&&
!
authenticationToken
.
hasExpired
(
)
&&
!
forceUpdate
)
{
return
authenticationToken
;
}
String
authString
=
getClientId
(
)
+
":"
+
getClientSecret
(
)
;
String
authentication
=
new
Base64
(
)
.
encodeToString
(
authString
.
getBytes
(
)
)
;
HttpHeaders
headers
=
new
HttpHeaders
(
)
;
headers
.
add
(
AUTHORIZATION
,
BASIC
+
" "
+
authentication
)
;
// form encode the grant_type...
MultiValueMap
<
String
,
String
>
form
=
new
LinkedMultiValueMap
<>
(
)
;
form
.
add
(
GRANT_TYPE
,
CLIENT_CREDENTIALS
)
;
HttpEntity
<
MultiValueMap
<
String
,
String
>
>
request
=
new
HttpEntity
<>
(
form
,
headers
)
;
URL
authURL
=
getAuthenticationURL
(
)
;
authenticationToken
=
restTemplate
.
postForObject
(
authURL
.
toExternalForm
(
)
,
request
,
SSPToken
.
class
)
;
return
authenticationToken
;
}

Get the authentication token to use.

@param forceUpdate if true, get a new auth token even if a cached instance exists.
@return The authentication token
@throws MalformedURLException if the authentication URL is invalid
@throws RestClientException if an error occurs when talking to SSP


@JsonIgnore
    @XmlTransient
    public int size() {
        return categories.stream()
                .map(NotificationCategory::getEntries)
                .mapToInt(List::size)
                .sum();
    }

@ JsonIgnore @ XmlTransient public int size ( ) { return categories . stream ( ) . map ( NotificationCategory :: getEntries ) . mapToInt ( List :: size ) . sum ( ) ; }

Provides the total number of notifications contained in the response.

Provides the total number of notifications contained in the response .


private void addCategories(List<NotificationCategory> newCategories) {
        if (newCategories == null) {
            return;
        }

        // Start with a deep copy of the method parameter to simplify remaining logic
        newCategories = newCategories.parallelStream().map(NotificationCategory::cloneNoExceptions).collect(Collectors.toList());

        // Create a map of current categories by title for processing
        Map<String, NotificationCategory> catsByName =
                this.categories.parallelStream().collect(toMap(c -> c.getTitle().toLowerCase(), c -> c));

        // Split new categories between those that match an existing category and those that are completely new
        Map<Boolean, List<NotificationCategory>> matchingNewCats =
                newCategories.stream()
                        .collect(partitioningBy(c -> catsByName.containsKey(c.getTitle().toLowerCase())));

        // Add new entries to existing categories
        matchingNewCats.get(Boolean.TRUE).stream()
                .forEachOrdered(c -> catsByName.get(c.getTitle().toLowerCase()).addEntries(c.getEntries()));

        // Add new categories
        this.categories.addAll(matchingNewCats.get(Boolean.FALSE));
    }

Insert the given categories and their entries into the any existing
categories of the same title. If a category doesn't match an existing
one, add it to the list.
@param newCategories collection of new categories and their entries.

Insert the given categories and their entries into the any existing categories of the same title . If a category doesn t match an existing one add it to the list .


@Override
    public NotificationResponse fetch(PortletRequest req) {
        PortletPreferences preferences = req.getPreferences();
        String enabled = preferences.getValue(SSP_NOTIFICATIONS_ENABLED, "false");
        if (!"true".equalsIgnoreCase(enabled)) {
            return new NotificationResponse();
        }

        String personId = getPersonId(req);
        if (personId == null) {
            // Not all students will have active SSP records,
            // so if no entry is found in SSP, just return an
            // empty response set.
            return new NotificationResponse();
        }

        String urlFragment = getActiveTaskUrl();
        SSPApiRequest<String> request = new SSPApiRequest<>(urlFragment, String.class)
                .addUriParameter("personId", personId);

        ResponseEntity<String> response;
        try {
            response = sspApi.doRequest(request);
        } catch (Exception e) {
            log.error("Error reading SSP Notifications: " + e.getMessage());
            return notificationError(e.getMessage());
        }

        if (response.getStatusCode().series() != HttpStatus.Series.SUCCESSFUL) {
            log.error("Error reading SSP Notifications: " + response);
            return notificationError(response.getBody());
        }

        NotificationResponse notification = mapToNotificationResponse(req, response);
        return notification;
    }

Fetch the set of SSP tasks for the uPortal user.

@param req The PortletRequest
@return The set of notifications for this data source.

Fetch the set of SSP tasks for the uPortal user .


private NotificationResponse notificationError(String errorMsg) {
        NotificationError error = new NotificationError();
        error.setError(errorMsg);
        error.setSource(getClass().getSimpleName());

        NotificationResponse notification = new NotificationResponse();
        notification.setErrors(Arrays.asList(error));
        return notification;
    }

private NotificationResponse notificationError ( String errorMsg ) { NotificationError error = new NotificationError ( ) ; error . setError ( errorMsg ) ; error . setSource ( getClass ( ) . getSimpleName ( ) ) ; NotificationResponse notification = new NotificationResponse ( ) ; notification . setErrors ( Arrays . asList ( error ) ) ; return notification ; }

Error handler.

@param errorMsg The error message
@return a notification response with the error message


private NotificationResponse mapToNotificationResponse(PortletRequest request, ResponseEntity<String> response) {
        Configuration config = Configuration.builder().options(
                Option.DEFAULT_PATH_LEAF_TO_NULL
        ).build();
        ReadContext readContext = JsonPath
                .using(config)
                .parse(response.getBody());

        // check the status embedded in the response too...
        String success = readContext.read(SUCCESS_QUERY);
        // grr. SSP returns this as a string...
        if (!"true".equalsIgnoreCase(success)) {
            String error = readContext.read(MESSAGE_QUERY);
            return notificationError(error);
        }

        // read the actual tasks...
        Object rows = readContext.read(ROWS_QUERY);
        if (!(rows instanceof JSONArray)) {
            throw new RuntimeException("Expected 'rows' to be an array of tasks");
        }

        String source = getNotificationSource(request);

        List<NotificationEntry> list = new ArrayList<>();
        for (int i = 0; i < ((JSONArray)rows).size(); i++) {
            NotificationEntry entry = mapNotificationEntry(readContext, i, source);
            if (entry != null) {
                attachActions(request, entry);
                list.add(entry);
            }
        }

        // build the notification response...
        NotificationResponse notification = new NotificationResponse();
        if (!list.isEmpty()) {
            NotificationCategory category = getNotificationCategory(request);
            category.addEntries(list);

            notification.setCategories(Arrays.asList(category));
        }

        return notification;
    }

Map and SSP Response to a NotificationResponse.

@param request the portlet request
@param response the response from the REST call to SSP
@return the mapped notification response

Map and SSP Response to a NotificationResponse .


private NotificationEntry mapNotificationEntry(ReadContext readContext, int index, String source) {
        boolean completed = readContext.read(format(ROW_COMPLETED_QUERY_FMT, index), Boolean.class);
        if (completed) {
            return null;
        }

        NotificationEntry entry = new NotificationEntry();
        entry.setSource(source);

        String id = readContext.read(format(ROW_ID_QUERY_FMT, index));
        entry.setId(id);

        String title = readContext.read(format(ROW_NAME_QUERY_FMT, index));
        entry.setTitle(title);

        String desc = readContext.read(format(ROW_DESCRIPTION_QUERY_FMT, index));
        entry.setBody(desc);

        String link = readContext.read(format(ROW_LINK_QUERY_FMT, index));
        URL fixedLink = normalizeLink(link);
        if (fixedLink != null) {
            entry.setUrl(fixedLink.toExternalForm());
        }

        Date createDate = readContext.read(format("$.rows[%d].createdDate", index), Date.class);
        Map<NotificationState, Date> states = new HashMap<>();
        states.put(NotificationState.ISSUED, createDate);

        try {
            // the date is in an odd format, need to parse by hand...
            String dateStr = readContext.read(format(ROW_DUE_DATE_QUERY_FMT, index));
            if (!StringUtils.isBlank(dateStr)) {
                synchronized (dateFormat) {
                    Date dueDate = dateFormat.parse(dateStr);
                    entry.setDueDate(dueDate);
                }
            }
        } catch (Exception e) {
            log.warn("Error parsing due date.  Ignoring", e);
        }

        return entry;
    }

Map a single notification entry.

@param readContext the parsed JSON from SSP
@param index the index of the current entry to read
@param source the source value to use for the entry
@return a new Notification Entry.  May return null if the entry is invalid or complete


private void attachActions(PortletRequest request, NotificationEntry entry) {
        PortletPreferences prefs = request.getPreferences();
        String stringVal = prefs.getValue(SSP_NOTIFICATIONS_ENABLE_MARK_COMPLETED, "false");
        boolean enableMarkCompleted = ("true".equalsIgnoreCase(stringVal));

        List<NotificationAction> actions = new ArrayList<>();

        if (enableMarkCompleted) {
            MarkTaskCompletedAction action = new MarkTaskCompletedAction(entry.getId());
            actions.add(action);
        }

        entry.setAvailableActions(actions);
    }

private void attachActions ( PortletRequest request , NotificationEntry entry ) { PortletPreferences prefs = request . getPreferences ( ) ; String stringVal = prefs . getValue ( SSP_NOTIFICATIONS_ENABLE_MARK_COMPLETED , "false" ) ; boolean enableMarkCompleted = ( "true" . equalsIgnoreCase ( stringVal ) ) ; List < NotificationAction > actions = new ArrayList <> ( ) ; if ( enableMarkCompleted ) { MarkTaskCompletedAction action = new MarkTaskCompletedAction ( entry . getId ( ) ) ; actions . add ( action ) ; } entry . setAvailableActions ( actions ) ; }

Attach any SSP specific actions to this entry, if enabled.

@param request the portlet request
@param entry the entry

Attach any SSP specific actions to this entry if enabled .


private URL normalizeLink(String link) {
        try {
            if (StringUtils.isEmpty(link)) {
                return null;
            }

            if (link.startsWith("/")) {
                return sspApi.getSSPUrl(link, true);
            }

            if (link.startsWith("http://") || link.startsWith("https://")) {
                return new URL(link);
            }

            // if all else fails, just tack on http:// and see if the URL parser can handle
            // it.  Perhaps, not ideal...
            return new URL("http://" + link);
        } catch (MalformedURLException e) {
            log.warn("Bad URL from SSP Entry: " + link, e);
            return null;
        }
    }

Some of the links I have seen from SSP are not well formed.   Try to convert any URLs
to a usable form.

@param link The link value from SSP
@return A full URL
@throws java.net.MalformedURLException if the link value can not be converted
to a URL.

Some of the links I have seen from SSP are not well formed . Try to convert any URLs to a usable form .


private NotificationCategory getNotificationCategory(PortletRequest request) {
        PortletPreferences preferences = request.getPreferences();
        String title = preferences.getValue(NOTIFICATION_CATEGORY_PREF, DEFAULT_CATEGORY);

        NotificationCategory category = new NotificationCategory();
        category.setTitle(title);

        return category;
    }

private NotificationCategory getNotificationCategory ( PortletRequest request ) { PortletPreferences preferences = request . getPreferences ( ) ; String title = preferences . getValue ( NOTIFICATION_CATEGORY_PREF , DEFAULT_CATEGORY ) ; NotificationCategory category = new NotificationCategory ( ) ; category . setTitle ( title ) ; return category ; }

Get the category name to use for SSP notifications.

@param request the portlet request
@return The notification category to use

Get the category name to use for SSP notifications .


private String getNotificationSource(PortletRequest req) {
        PortletPreferences preferences = req.getPreferences();
        String source = preferences.getValue(NOTIFICATION_SOURCE_PREF, DEFAULT_NOTIFICATION_SOURCE);

        return source;
    }

private String getNotificationSource ( PortletRequest req ) { PortletPreferences preferences = req . getPreferences ( ) ; String source = preferences . getValue ( NOTIFICATION_SOURCE_PREF , DEFAULT_NOTIFICATION_SOURCE ) ; return source ; }

Get the source value to use for a Notification entry.

@param req the portlet request
@return the source value to use.

Get the source value to use for a Notification entry .


@Override
    public void invoke(final ActionRequest req, final ActionResponse res) throws IOException {
        final NotificationEntry entry = getTarget();
        final String notificationId = entry.getId();
        final Set<String> readNotices = this.getReadNotices(req);
        if (readNotices.contains(notificationId)) {
            readNotices.remove(notificationId);
        } else {
            readNotices.add(notificationId);
        }
        setReadNotices(req, readNotices);
    }

@ Override public void invoke ( final ActionRequest req , final ActionResponse res ) throws IOException { final NotificationEntry entry = getTarget ( ) ; final String notificationId = entry . getId ( ) ; final Set < String > readNotices = this . getReadNotices ( req ) ; if ( readNotices . contains ( notificationId ) ) { readNotices . remove ( notificationId ) ; } else { readNotices . add ( notificationId ) ; } setReadNotices ( req , readNotices ) ; }


private NotificationResponse readFromFile(String filename) {

        NotificationResponse rslt;

        logger.debug("Preparing to read from file:  {}", filename);

        URL location = getClass().getClassLoader().getResource(filename);
        if (location != null) {
            try {
                File f = new File(location.toURI());
                rslt =  mapper.readValue(f, NotificationResponse.class);
            } catch (Exception e) {
                String msg = "Failed to read the data file:  " + location;
                logger.error(msg, e);
                rslt = prepareErrorResponse(getName(), msg);
            }
        } else {
            String msg = "Data file not found:  " + filename;
            rslt = prepareErrorResponse(getName(), msg);
        }

        return rslt;

    }

private
NotificationResponse
readFromFile
(
String
filename
)
{
NotificationResponse
rslt
;
logger
.
debug
(
"Preparing to read from file:  {}"
,
filename
)
;
URL
location
=
getClass
(
)
.
getClassLoader
(
)
.
getResource
(
filename
)
;
if
(
location
!=
)
{
try
{
File
f
=
new
File
(
location
.
toURI
(
)
)
;
rslt
=
mapper
.
readValue
(
f
,
NotificationResponse
.
class
)
;
}
catch
(
Exception
e
)
{
String
msg
=
"Failed to read the data file:  "
+
location
;
logger
.
error
(
msg
,
e
)
;
rslt
=
prepareErrorResponse
(
getName
(
)
,
msg
)
;
}
}
else
{
String
msg
=
"Data file not found:  "
+
filename
;
rslt
=
prepareErrorResponse
(
getName
(
)
,
msg
)
;
}
return
rslt
;
}

Deserialize the given JSON formatted file back into a object.

@param filename The path and name of the file to be read.
@return NotificationRequest, null if the de-serialization fails.

Deserialize the given JSON formatted file back into a object .


private String getSchoolId(PortletRequest request) {
        PortletPreferences prefs = request.getPreferences();
        String schoolIdAttributeName = prefs.getValue("SSPTaskNotificationService.schoolIdAttribute", "schoolId");

        Map<String, String> userInfo = (Map<String, String>)request.getAttribute(PortletRequest.USER_INFO);
        String studentId = userInfo.get(schoolIdAttributeName);
        if (!StringUtils.isEmpty(studentId)) {
            return studentId;
        }

        // if not found, fall back to username.
        studentId = userInfo.get(USERNAME_ATTRIBUTE);

        return studentId;
    }

Get the schoolId value from the request.

@param request the portlet request
@return the school id.   May return null if the school id is not available

Get the schoolId value from the request .


private String extractUserId(String studentId, ResponseEntity<String> response) {
        Configuration config = Configuration.builder().options(
                Option.DEFAULT_PATH_LEAF_TO_NULL
        ).build();
        ReadContext readContext = JsonPath
                .using(config)
                .parse(response.getBody());

        String success = readContext.read(SUCCESS_QUERY);
        // SSP passes this as a string...
        if (!"true".equalsIgnoreCase(success)) {
            return null;
        }

        int count = readContext.read(RESULTS_QUERY, Integer.class);
        if (count != 1) {
            // couldn't find a single unique result.  Bail now...
            log.warn("Expected a single unique result for " + studentId + ".  Found " + count);
            return null;
        }

        String id = readContext.read(STUDENT_ID_QUERY);
        return id;
    }

Parse the person lookup response from SSP.

@param studentId the uPortal studentid
@param response the SSP response
@return the SSP id if available, else null

Parse the person lookup response from SSP .


@Override
    public void invoke(final ActionRequest req, final ActionResponse res) throws IOException {

        final NotificationEntry entry = getTarget();

        /*
         * The HideAction works like a toggle
         */
        if (!isEntrySnoozed(entry, req)) {
            // Hide it...
            hide(entry, req);
        } else {
            // Un-hide it...
            unhide(entry, req);
        }

    }


@JsonIgnore
    public Map<String,List<String>> getAttributesMap() {
        Map<String,List<String>> rslt = new HashMap<>();
        for (NotificationAttribute a : attributes) {
            rslt.put(a.getName(), a.getValues());
        }
        return rslt;
    }

@ JsonIgnore public Map < String , List < String > > getAttributesMap ( ) { Map < String , List < String > > rslt = new HashMap <> ( ) ; for ( NotificationAttribute a : attributes ) { rslt . put ( a . getName ( ) , a . getValues ( ) ) ; } return rslt ; }

Convenience method for obtaining the attributes in a more usable collection.

Convenience method for obtaining the attributes in a more usable collection .


public static void normalize( GeoTuple3D_F64 p ) {
		double n = p.norm();
		p.x /= n;
		p.y /= n;
		p.z /= n;
	}

public static void normalize ( GeoTuple3D_F64 p ) { double n = p . norm ( ) ; p . x /= n ; p . y /= n ; p . z /= n ; }

Normalizes the point such that the Frobenius norm is 1.

@param p

Normalizes the point such that the Frobenius norm is 1 .


public static boolean contained( Box3D_I32 boxA , Box3D_I32 boxB ) {
		return( boxA.p0.x <= boxB.p0.x && boxA.p1.x >= boxB.p1.x &&
				boxA.p0.y <= boxB.p0.y && boxA.p1.y >= boxB.p1.y &&
				boxA.p0.z <= boxB.p0.z && boxA.p1.z >= boxB.p1.z );
	}

public static boolean contained ( Box3D_I32 boxA , Box3D_I32 boxB ) { return ( boxA . p0 . x <= boxB . p0 . x && boxA . p1 . x >= boxB . p1 . x && boxA . p0 . y <= boxB . p0 . y && boxA . p1 . y >= boxB . p1 . y && boxA . p0 . z <= boxB . p0 . z && boxA . p1 . z >= boxB . p1 . z ) ; }

Returns true if boxB is contained inside of or is identical to boxA.

@param boxA Box
@param boxB Box which is being tested to see if it is inside of boxA
@return true if inside/identical or false if outside

Returns true if boxB is contained inside of or is identical to boxA .


public void closestPoint(Point3D_F64 P , Point3D_F64 closestPt ) {

		// D = B-P
		GeometryMath_F64.sub(B, P, D);

		a = E0.dot(E0);
		b = E0.dot(E1);
		c = E1.dot(E1);
		d = E0.dot(D);
		e = E1.dot(D);

		double det = a * c - b * b;
		s = b * e - c * d;
		t = b * d - a * e;

		if (s + t <= det) {
			if (s < 0) {
				if (t < 0) {
					region4();
				} else {
					region3();
				}
			} else if (t < 0) {
				region5();
			} else {
				region0(det);
			}
		} else {
			if (s < 0) {
				region2();
			} else if (t < 0) {
				region6();
			} else {
				region1();
			}
		}

		closestPt.x = B.x + s*E0.x  + t*E1.x;
		closestPt.y = B.y + s*E0.y  + t*E1.y;
		closestPt.z = B.z + s*E0.z  + t*E1.z;
	}

public
void
closestPoint
(
Point3D_F64
P
,
Point3D_F64
closestPt
)
{
// D = B-P
GeometryMath_F64
.
sub
(
B
,
P
,
D
)
;
a
=
E0
.
dot
(
E0
)
;
b
=
E0
.
dot
(
E1
)
;
c
=
E1
.
dot
(
E1
)
;
d
=
E0
.
dot
(
D
)
;
e
=
E1
.
dot
(
D
)
;
double
det
=
a
*
c
b
*
b
;
s
=
b
*
e
c
*
d
;
t
=
b
*
d
a
*
e
;
if
(
s
+
t
<=
det
)
{
if
(
s
<
0
)
{
if
(
t
<
0
)
{
region4
(
)
;
}
else
{
region3
(
)
;
}
}
else
if
(
t
<
0
)
{
region5
(
)
;
}
else
{
region0
(
det
)
;
}
}
else
{
if
(
s
<
0
)
{
region2
(
)
;
}
else
if
(
t
<
0
)
{
region6
(
)
;
}
else
{
region1
(
)
;
}
}
closestPt
.
x
=
B
.
x
+
s
*
E0
.
x
+
t
*
E1
.
x
;
closestPt
.
y
=
B
.
y
+
s
*
E0
.
y
+
t
*
E1
.
y
;
closestPt
.
z
=
B
.
z
+
s
*
E0
.
z
+
t
*
E1
.
z
;
}

Find the closest point on the triangle to P.
@param P Input.  The point for which the closest point is to be found.
@param closestPt Output.  The found closest point.

Find the closest point on the triangle to P .


public double sign( Point3D_F64 P ) {

		GeometryMath_F64.cross(E1,E0,N);

		// dot product of
		double d = N.x*(P.x-B.x) + N.y*(P.y-B.y) + N.z*(P.z-B.z);
		return Math.signum(d);
	}

public
double
sign
(
Point3D_F64
P
)
{
GeometryMath_F64
.
cross
(
E1
,
E0
,
N
)
;
// dot product of
double
d
=
N
.
x
*
(
P
.
x
B
.
x
)
+
N
.
y
*
(
P
.
y
B
.
y
)
+
N
.
z
*
(
P
.
z
B
.
z
)
;
return
Math
.
signum
(
d
)
;
}

Returns the signed of the vector.  If its "in front" it will be positive and negative if "behind".  In front
is defined as being on the same side as the cross product of p2-p0 and p1-p0.

@param P Point for which the sign is determined

Returns the signed of the vector . If its in front it will be positive and negative if behind . In front is defined as being on the same side as the cross product of p2 - p0 and p1 - p0 .


public void set( Se3_F64 se ) {
		R.set( se.getR() );
		T.set( se.getT() );
	}

public void set ( Se3_F64 se ) { R . set ( se . getR ( ) ) ; T . set ( se . getT ( ) ) ; }

Set's 'this' Se3_F64 to be identical to the provided transform.

@param se The transform that is being copied.

Set s this Se3_F64 to be identical to the provided transform .


public void set(double x , double y , double z , EulerType type , double rotA , double rotB , double rotC ) {
		T.set(x,y,z);
		ConvertRotation3D_F64.eulerToMatrix(type,rotA,rotB,rotC,R);
	}

public void set ( double x , double y , double z , EulerType type , double rotA , double rotB , double rotC ) { T . set ( x , y , z ) ; ConvertRotation3D_F64 . eulerToMatrix ( type , rotA , rotB , rotC , R ) ; }

Fully specify the transform using Euler angles


public static void convert( Rectangle2D_F64 input , Polygon2D_F64 output ) {
		if (output.size() != 4)
			throw new IllegalArgumentException("polygon of order 4 expected");

		output.get(0).set(input.p0.x, input.p0.y);
		output.get(1).set(input.p1.x, input.p0.y);
		output.get(2).set(input.p1.x, input.p1.y);
		output.get(3).set(input.p0.x, input.p1.y);
	}

public
static
void
convert
(
Rectangle2D_F64
input
,
Polygon2D_F64
output
)
{
if
(
output
.
size
(
)
!=
4
)
throw
new
IllegalArgumentException
(
"polygon of order 4 expected"
)
;
output
.
get
(
0
)
.
set
(
input
.
p0
.
x
,
input
.
p0
.
y
)
;
output
.
get
(
1
)
.
set
(
input
.
p1
.
x
,
input
.
p0
.
y
)
;
output
.
get
(
2
)
.
set
(
input
.
p1
.
x
,
input
.
p1
.
y
)
;
output
.
get
(
3
)
.
set
(
input
.
p0
.
x
,
input
.
p1
.
y
)
;
}

Converts a rectangle into a polygon

@param input Rectangle.
@param output Polygon2D_F64.  Modified.


public static void convert( Polygon2D_F64 input , Quadrilateral_F64 output ) {
		if( input.size() != 4 )
			throw new IllegalArgumentException("Expected 4-sided polygon as input");

		output.a.set(input.get(0));
		output.b.set(input.get(1));
		output.c.set(input.get(2));
		output.d.set(input.get(3));
	}

public
static
void
convert
(
Polygon2D_F64
input
,
Quadrilateral_F64
output
)
{
if
(
input
.
size
(
)
!=
4
)
throw
new
IllegalArgumentException
(
"Expected 4-sided polygon as input"
)
;
output
.
a
.
set
(
input
.
get
(
0
)
)
;
output
.
b
.
set
(
input
.
get
(
1
)
)
;
output
.
c
.
set
(
input
.
get
(
2
)
)
;
output
.
d
.
set
(
input
.
get
(
3
)
)
;
}

Converts a polygon into a quadrilateral

@param input polygon.
@param output Quadrilateral.  Modified.


public static void bounding( Quadrilateral_F64 quad , Rectangle2D_F64 rectangle ) {

		rectangle.p0.x = Math.min(quad.a.x,quad.b.x);
		rectangle.p0.x = Math.min(rectangle.p0.x,quad.c.x);
		rectangle.p0.x = Math.min(rectangle.p0.x,quad.d.x);

		rectangle.p0.y = Math.min(quad.a.y,quad.b.y);
		rectangle.p0.y = Math.min(rectangle.p0.y,quad.c.y);
		rectangle.p0.y = Math.min(rectangle.p0.y,quad.d.y);

		rectangle.p1.x = Math.max(quad.a.x,quad.b.x);
		rectangle.p1.x = Math.max(rectangle.p1.x,quad.c.x);
		rectangle.p1.x = Math.max(rectangle.p1.x,quad.d.x);

		rectangle.p1.y = Math.max(quad.a.y,quad.b.y);
		rectangle.p1.y = Math.max(rectangle.p1.y,quad.c.y);
		rectangle.p1.y = Math.max(rectangle.p1.y,quad.d.y);
	}

public static void bounding ( Quadrilateral_F64 quad , Rectangle2D_F64 rectangle ) { rectangle . p0 . x = Math . min ( quad . a . x , quad . b . x ) ; rectangle . p0 . x = Math . min ( rectangle . p0 . x , quad . c . x ) ; rectangle . p0 . x = Math . min ( rectangle . p0 . x , quad . d . x ) ; rectangle . p0 . y = Math . min ( quad . a . y , quad . b . y ) ; rectangle . p0 . y = Math . min ( rectangle . p0 . y , quad . c . y ) ; rectangle . p0 . y = Math . min ( rectangle . p0 . y , quad . d . y ) ; rectangle . p1 . x = Math . max ( quad . a . x , quad . b . x ) ; rectangle . p1 . x = Math . max ( rectangle . p1 . x , quad . c . x ) ; rectangle . p1 . x = Math . max ( rectangle . p1 . x , quad . d . x ) ; rectangle . p1 . y = Math . max ( quad . a . y , quad . b . y ) ; rectangle . p1 . y = Math . max ( rectangle . p1 . y , quad . c . y ) ; rectangle . p1 . y = Math . max ( rectangle . p1 . y , quad . d . y ) ; }

Finds the minimum area bounding rectangle around the quadrilateral.

@param quad (Input) Quadrilateral
@param rectangle (Output) Minimum area rectangle

Finds the minimum area bounding rectangle around the quadrilateral .


public static void bounding( Polygon2D_F64 polygon , Rectangle2D_F64 rectangle ) {

		rectangle.p0.set(polygon.get(0));
		rectangle.p1.set(polygon.get(0));

		for (int i = 0; i < polygon.size(); i++) {
			Point2D_F64 p = polygon.get(i);
			if( p.x < rectangle.p0.x ) {
				rectangle.p0.x = p.x;
			} else if( p.x > rectangle.p1.x ) {
				rectangle.p1.x = p.x;
			}

			if( p.y < rectangle.p0.y ) {
				rectangle.p0.y = p.y;
			} else if( p.y > rectangle.p1.y ) {
				rectangle.p1.y = p.y;
			}
		}
	}

public static void bounding ( Polygon2D_F64 polygon , Rectangle2D_F64 rectangle ) { rectangle . p0 . set ( polygon . get ( 0 ) ) ; rectangle . p1 . set ( polygon . get ( 0 ) ) ; for ( int i = 0 ; i < polygon . size ( ) ; i ++ ) { Point2D_F64 p = polygon . get ( i ) ; if ( p . x < rectangle . p0 . x ) { rectangle . p0 . x = p . x ; } else if ( p . x > rectangle . p1 . x ) { rectangle . p1 . x = p . x ; } if ( p . y < rectangle . p0 . y ) { rectangle . p0 . y = p . y ; } else if ( p . y > rectangle . p1 . y ) { rectangle . p1 . y = p . y ; } } }

Finds the minimum area bounding rectangle around the quadrilateral that is aligned with coordinate
system axises.

@param polygon (Input) Polygon
@param rectangle (Output) Minimum area rectangle

Finds the minimum area bounding rectangle around the quadrilateral that is aligned with coordinate system axises .


public static Point2D_F64 center( Quadrilateral_F64 quad , Point2D_F64 center ) {
		if( center == null )
			center = new Point2D_F64();

		center.x = quad.a.x + quad.b.x + quad.c.x + quad.d.x;
		center.y = quad.a.y + quad.b.y + quad.c.y + quad.d.y;

		center.x /= 4.0;
		center.y /= 4.0;

		return center;
	}

public static Point2D_F64 center ( Quadrilateral_F64 quad , Point2D_F64 center ) { if ( center == null ) center = new Point2D_F64 ( ) ; center . x = quad . a . x + quad . b . x + quad . c . x + quad . d . x ; center . y = quad . a . y + quad . b . y + quad . c . y + quad . d . y ; center . x /= 4.0 ; center . y /= 4.0 ; return center ; }

Computes the center or average point in the quadrilateral.

@param quad (Input) Quadrilateral
@param center (output) Center point of the quadrilateral.  Can be null.
@return The center point.

Computes the center or average point in the quadrilateral .


public static void vertexAverage(Polygon2D_F64 input, Point2D_F64 average ) {
		average.setIdx(0,0);

		for (int i = 0; i < input.size(); i++) {
			Point2D_F64 v = input.vertexes.data[i];
			average.x += v.x;
			average.y += v.y;
		}
		average.x /= input.size();
		average.y /= input.size();
	}

public static void vertexAverage ( Polygon2D_F64 input , Point2D_F64 average ) { average . setIdx ( 0 , 0 ) ; for ( int i = 0 ; i < input . size ( ) ; i ++ ) { Point2D_F64 v = input . vertexes . data [ i ] ; average . x += v . x ; average . y += v . y ; } average . x /= input . size ( ) ; average . y /= input . size ( ) ; }

Computes the average of all the vertexes
@param input (input) polygon
@param average (output) average point


public static void convexHull( List<Point2D_F64> points , Polygon2D_F64 hull ) {
		Point2D_F64[] array = new Point2D_F64[points.size()];

		for (int i = 0; i < points.size(); i++) {
			array[i] = points.get(i);
		}

		AndrewMonotoneConvexHull_F64 andrew = new AndrewMonotoneConvexHull_F64();
		andrew.process(array,array.length,hull);
	}

public static void convexHull ( List < Point2D_F64 > points , Polygon2D_F64 hull ) { Point2D_F64 [ ] array = new Point2D_F64 [ points . size ( ) ] ; for ( int i = 0 ; i < points . size ( ) ; i ++ ) { array [ i ] = points . get ( i ) ; } AndrewMonotoneConvexHull_F64 andrew = new AndrewMonotoneConvexHull_F64 ( ) ; andrew . process ( array , array . length , hull ) ; }

Computes the convex hull of the set of points.

<p>
NOTE: This method declares a temporary array.  If you want to avoid that invoke {@link AndrewMonotoneConvexHull_F64}
directly.
</p>

@param points (Input) Set of points.
@param hull (output) storage for convex hull.  Will be in counter-clockwise order

Computes the convex hull of the set of points .


public static void removeAlmostParallel( Polygon2D_F64 polygon , double tol ) {

		for (int i = 0; i < polygon.vertexes.size(); ) {
			int j = (i+1)%polygon.vertexes.size();
			int k = (i+2)%polygon.vertexes.size();

			Point2D_F64 p0 = polygon.vertexes.get(i);
			Point2D_F64 p1 = polygon.vertexes.get(j);
			Point2D_F64 p2 = polygon.vertexes.get(k);

			double angle = UtilVector2D_F64.acute(p1.x-p0.x,p1.y-p0.y,p2.x-p1.x,p2.y-p1.y);

			if( angle <= tol) {
				polygon.vertexes.remove(j);
				if( j < i )
					i = polygon.vertexes.size()-1;
			} else {
				i++;
			}
		}
	}

public static void removeAlmostParallel ( Polygon2D_F64 polygon , double tol ) { for ( int i = 0 ; i < polygon . vertexes . size ( ) ; ) { int j = ( i + 1 ) % polygon . vertexes . size ( ) ; int k = ( i + 2 ) % polygon . vertexes . size ( ) ; Point2D_F64 p0 = polygon . vertexes . get ( i ) ; Point2D_F64 p1 = polygon . vertexes . get ( j ) ; Point2D_F64 p2 = polygon . vertexes . get ( k ) ; double angle = UtilVector2D_F64 . acute ( p1 . x - p0 . x , p1 . y - p0 . y , p2 . x - p1 . x , p2 . y - p1 . y ) ; if ( angle <= tol ) { polygon . vertexes . remove ( j ) ; if ( j < i ) i = polygon . vertexes . size ( ) - 1 ; } else { i ++ ; } } }

Removes a node from a polygon if the two lines its attached two are almost parallel
@param polygon The polygon being modified
@param tol Tolerance in radians


public static double averageOfClosestPointError(Polygon2D_F64 model , Polygon2D_F64 target , int numberOfSamples ) {
		LineSegment2D_F64 line = new LineSegment2D_F64();

		double cornerLocationsB[] = new double[target.size()+1];
		double totalLength = 0;
		for (int i = 0; i < target.size(); i++) {
			Point2D_F64 b0 = target.get(i%target.size());
			Point2D_F64 b1 = target.get((i+1)%target.size());

			cornerLocationsB[i] = totalLength;
			totalLength += b0.distance(b1);
		}
		cornerLocationsB[target.size()] = totalLength;

		Point2D_F64 pointOnB = new Point2D_F64();
		double error = 0;
		int cornerB = 0;
		for (int k = 0; k < numberOfSamples; k++) {
			// Find the point on B to match to a point on A
			double location = totalLength*k/numberOfSamples;

			while (location > cornerLocationsB[cornerB + 1]) {
				cornerB++;
			}
			Point2D_F64 b0 = target.get(cornerB);
			Point2D_F64 b1 = target.get((cornerB+1)%target.size());

			double locationCornerB = cornerLocationsB[cornerB];
			double fraction = (location-locationCornerB)/(cornerLocationsB[cornerB+1]-locationCornerB);

			pointOnB.x = (b1.x-b0.x)*fraction + b0.x;
			pointOnB.y = (b1.y-b0.y)*fraction + b0.y;

			// find the best fit point on A to the point in B
			double best = Double.MAX_VALUE;
			for (int i = 0; i < model.size()+1; i++) {
				line.a = model.get(i%model.size());
				line.b = model.get((i+1)%model.size());

				double d = Distance2D_F64.distance(line,pointOnB);
				if( d < best ) {
					best = d;
				}
			}
			error += best;
		}

		return error/numberOfSamples;
	}

Compute the error as a function of the distance between the model and target. The target is sampled at regular
intervals and for each of these points the closest point on the model is found. The returned metric is the
average of difference between paired points.

NOTE: A different answer will be returned depending on which polygon is the model and which one is the target.

@param model Model polygon
@param target Target polygon
@return average of closest point error

Compute the error as a function of the distance between the model and target . The target is sampled at regular intervals and for each of these points the closest point on the model is found . The returned metric is the average of difference between paired points .


public double computeArea(Polygon2D_F64 a , Polygon2D_F64 b ) {
		ssss = 0;
		sclx = 0;
		scly = 0;
		return inter(a,b);
	}

public double computeArea ( Polygon2D_F64 a , Polygon2D_F64 b ) { ssss = 0 ; sclx = 0 ; scly = 0 ; return inter ( a , b ) ; }

Computes the area of the intersection between the two polygons.

Note: the area result has little more accuracy than a float
This is true even if the polygon is specified with doubles.

@param a Polygon A
@param b Polygon B
@return area of area of intersection. Negative if the order (CW vs CCW) do not match.

Computes the area of the intersection between the two polygons .


public static boolean contains( Quadrilateral_F64 quad , Point2D_F64 pt ) {
		return containTriangle(quad.a, quad.b, quad.d, pt) ||
				containTriangle(quad.b, quad.c, quad.d, pt);

	}

public static boolean contains ( Quadrilateral_F64 quad , Point2D_F64 pt ) { return containTriangle ( quad . a , quad . b , quad . d , pt ) || containTriangle ( quad . b , quad . c , quad . d , pt ) ; }

True if the point is contained inside the quadrilateral.

@param quad quadrilateral
@param pt point
@return true if the point is inside and false if it is not.

True if the point is contained inside the quadrilateral .


public static boolean containTriangle( Point2D_F64 a , Point2D_F64 b , Point2D_F64 c , Point2D_F64 pt )
	{
		boolean ret = false;

		if ( ((a.y>pt.y) != (b.y>pt.y)) && (pt.x < (b.x-a.x) * (pt.y-a.y) / (b.y-a.y) + a.x) )
			ret = true;

		if ( ((b.y>pt.y) != (c.y>pt.y)) && (pt.x < (c.x-b.x) * (pt.y-b.y) / (c.y-b.y) + b.x) )
			ret = !ret;

		if ( ((c.y>pt.y) != (a.y>pt.y)) && (pt.x < (a.x-c.x) * (pt.y-c.y) / (a.y-c.y) + c.x) )
			ret = !ret;

		return ret;
	}

public static boolean containTriangle ( Point2D_F64 a , Point2D_F64 b , Point2D_F64 c , Point2D_F64 pt ) { boolean ret = false ; if ( ( ( a . y > pt . y ) != ( b . y > pt . y ) ) && ( pt . x < ( b . x - a . x ) * ( pt . y - a . y ) / ( b . y - a . y ) + a . x ) ) ret = true ; if ( ( ( b . y > pt . y ) != ( c . y > pt . y ) ) && ( pt . x < ( c . x - b . x ) * ( pt . y - b . y ) / ( c . y - b . y ) + b . x ) ) ret = ! ret ; if ( ( ( c . y > pt . y ) != ( a . y > pt . y ) ) && ( pt . x < ( a . x - c . x ) * ( pt . y - c . y ) / ( a . y - c . y ) + c . x ) ) ret = ! ret ; return ret ; }

Returns true of the the point is inside the triangle.

This function is simply an unrolled version of {@link #containConcave(Polygon2D_F64, Point2D_F64)}.

@param a vertex in triangle
@param b vertex in triangle
@param c vertex in triangle
@param pt Point which is being tested for containment inside of triangle
@return true if the point is inside of triangle

Returns true of the the point is inside the triangle .


public static Point2D_F64 intersection( LineParametric2D_F64 a, LineParametric2D_F64 b , Point2D_F64 ret ) {
		double t_b = a.getSlopeX() * ( b.getY() - a.getY() ) - a.getSlopeY() * ( b.getX() - a.getX() );
		double bottom = a.getSlopeY() * b.getSlopeX() - b.getSlopeY() * a.getSlopeX();

		if( bottom == 0 )
			return null;

		t_b /= bottom;

		double x = b.getSlopeX() * t_b + b.getX();
		double y = b.getSlopeY() * t_b + b.getY();

		if( ret == null )
			ret = new Point2D_F64();
		ret.set(x,y);
		return ret;
	}

public static Point2D_F64 intersection ( LineParametric2D_F64 a , LineParametric2D_F64 b , Point2D_F64 ret ) { double t_b = a . getSlopeX ( ) * ( b . getY ( ) - a . getY ( ) ) - a . getSlopeY ( ) * ( b . getX ( ) - a . getX ( ) ) ; double bottom = a . getSlopeY ( ) * b . getSlopeX ( ) - b . getSlopeY ( ) * a . getSlopeX ( ) ; if ( bottom == 0 ) return null ; t_b /= bottom ; double x = b . getSlopeX ( ) * t_b + b . getX ( ) ; double y = b . getSlopeY ( ) * t_b + b . getY ( ) ; if ( ret == null ) ret = new Point2D_F64 ( ) ; ret . set ( x , y ) ; return ret ; }

Finds the point of intersection between two lines and returns the point.

@param a Line.
@param b Line.
@param ret storage for the point of intersection. If null a new point will be declared.
@return If the two lines intersect it returns the point of intersection.  null if they don't intersect or have infinite intersections.

Finds the point of intersection between two lines and returns the point .


public static Point2D_F64 intersection( LineSegment2D_F64 l_0, LineSegment2D_F64 l_1,
											Point2D_F64 ret ) {

		double a0 = l_0.b.x - l_0.a.x;
		double b0 = l_0.b.y - l_0.a.y;
		double a1 = l_1.b.x - l_1.a.x;
		double b1 = l_1.b.y - l_1.a.y;

		double top = b0 * ( l_1.a.x - l_0.a.x ) + a0 * ( l_0.a.y - l_1.a.y );
		double bottom = a0 * b1 - b0 * a1;

		if( bottom == 0 )
			return null;
		double t_1 = top / bottom;

		// does not intersect along the second line segment
		if( t_1 < 0 || t_1 > 1 )
			return null;

		top = b1 * ( l_0.a.x - l_1.a.x ) + a1 * ( l_1.a.y - l_0.a.y );
		bottom = a1 * b0 - b1 * a0;

		double t_0 = top / bottom;

		// does not intersect along the first line segment
		if( t_0 < 0 || t_0 > 1 )
			return null;

		if( ret == null ) {
			ret = new Point2D_F64();
		}

		ret.set( l_1.a.x + a1 * t_1, l_1.a.y + b1 * t_1 );

		return ret;
	}

Finds the point of intersection between two lines segments.

@param l_0 Line segment.
@param l_1 line segment.
@param ret storage for the point of intersection. If null a new point will be declared.
@return If the two lines intersect it returns the point of intersection.  null if they don't intersect or have infinite intersections.

Finds the point of intersection between two lines segments .


public static Point2D_F64 intersection( Point2D_F64 lineA0 , Point2D_F64 lineA1 ,
											Point2D_F64 lineB0 , Point2D_F64 lineB1 ,
											Point2D_F64 output )
	{
		if( output == null )
			output = new Point2D_F64();

		double slopeAx = lineA1.x - lineA0.x;
		double slopeAy = lineA1.y - lineA0.y;
		double slopeBx = lineB1.x - lineB0.x;
		double slopeBy = lineB1.y - lineB0.y;

		double top = slopeAy * ( lineB0.x - lineA0.x ) + slopeAx * (lineA0.y - lineB0.y );
		double bottom = slopeAx * slopeBy - slopeAy * slopeBx;

		if( bottom == 0 )
			return null;
		double t = top / bottom;

		output.x = lineB0.x + t*slopeBx;
		output.y = lineB0.y + t*slopeBy;

		return output;
	}

public static Point2D_F64 intersection ( Point2D_F64 lineA0 , Point2D_F64 lineA1 , Point2D_F64 lineB0 , Point2D_F64 lineB1 , Point2D_F64 output ) { if ( output == null ) output = new Point2D_F64 ( ) ; double slopeAx = lineA1 . x - lineA0 . x ; double slopeAy = lineA1 . y - lineA0 . y ; double slopeBx = lineB1 . x - lineB0 . x ; double slopeBy = lineB1 . y - lineB0 . y ; double top = slopeAy * ( lineB0 . x - lineA0 . x ) + slopeAx * ( lineA0 . y - lineB0 . y ) ; double bottom = slopeAx * slopeBy - slopeAy * slopeBx ; if ( bottom == 0 ) return null ; double t = top / bottom ; output . x = lineB0 . x + t * slopeBx ; output . y = lineB0 . y + t * slopeBy ; return output ; }

Finds the point of intersection between the two lines defined by the set sets of points passed in.
@param lineA0 Point on line A
@param lineA1 Point on line A
@param lineB0 Point on line B
@param lineB1 Point on line B
@param output (Optional) storage for point of intersection
@return Point of intersection or null if the lines are parallel

Finds the point of intersection between the two lines defined by the set sets of points passed in .


public static double intersection( LineParametric2D_F64 target, LineSegment2D_F64 l ) {
		double a1 = l.b.x - l.a.x;
		double b1 = l.b.y - l.a.y;

		double top = target.slope.y * ( l.a.x - target.p.x ) + target.slope.x * ( target.p.y - l.a.y );
		double bottom = target.slope.x * b1 - target.slope.y * a1;

		if( bottom == 0 )
			return Double.NaN;

		double t_1 = top / bottom;

		// does not intersect along the second line segment
		if( t_1 < 0 || t_1 > 1 )
			return Double.NaN;

		top = b1 * ( target.p.x - l.a.x ) + a1 * ( l.a.y - target.p.y );
		bottom = a1 * target.slope.y - b1 * target.slope.x;

		return top / bottom;
	}

Finds the point of intersection between a line and a line segment.  The point of intersection
is specified as the distance along the parametric line.  If no intersection is found then
Double.NaN is returned.

@param target A line whose location along which the point of intersection is being found.  Not modified.
@param l	  Line segment which is being tested for intersection. Not modified.
@return The location along 'target'.  If the lines do not intersect or have infinite intersections Double.NaN is returned.

Finds the point of intersection between a line and a line segment . The point of intersection is specified as the distance along the parametric line . If no intersection is found then Double . NaN is returned .


public static double intersection( Polygon2D_F64 a , Polygon2D_F64 b ) {
		AreaIntersectionPolygon2D_F64 alg = new AreaIntersectionPolygon2D_F64();
		return Math.abs(alg.computeArea(a,b));
	}

public static double intersection ( Polygon2D_F64 a , Polygon2D_F64 b ) { AreaIntersectionPolygon2D_F64 alg = new AreaIntersectionPolygon2D_F64 ( ) ; return Math . abs ( alg . computeArea ( a , b ) ) ; }

Finds the area of the intersection of two polygons.

@see AreaIntersectionPolygon2D_F64

@param a (Input) Polygon 2D
@param b (Input) Polygon 2D
@return Area of intersection.

Finds the area of the intersection of two polygons .


public static boolean contains(EllipseRotated_F64 ellipse , double x , double y ) {
		return (UtilEllipse_F64.evaluate(x,y, ellipse) <= 1.0 );
	}

public static boolean contains ( EllipseRotated_F64 ellipse , double x , double y ) { return ( UtilEllipse_F64 . evaluate ( x , y , ellipse ) <= 1.0 ) ; }

Tests to see if the provided point lies on or is contained inside the ellipse

@param ellipse  Ellipse
@param x x-coordinate of point being tested for containment
@param y y-coordinate of point being tested for containment
@return true if inside and false if output


public static double intersectionArea( Rectangle2D_F64 a , Rectangle2D_F64 b ) {
		if( !intersects(a,b) )
			return 0;

		double x0 = Math.max(a.p0.x,b.p0.x);
		double x1 = Math.min(a.p1.x,b.p1.x);
		double y0 = Math.max(a.p0.y,b.p0.y);
		double y1 = Math.min(a.p1.y,b.p1.y);

		return (x1-x0)*(y1-y0);
	}

public static double intersectionArea ( Rectangle2D_F64 a , Rectangle2D_F64 b ) { if ( ! intersects ( a , b ) ) return 0 ; double x0 = Math . max ( a . p0 . x , b . p0 . x ) ; double x1 = Math . min ( a . p1 . x , b . p1 . x ) ; double y0 = Math . max ( a . p0 . y , b . p0 . y ) ; double y1 = Math . min ( a . p1 . y , b . p1 . y ) ; return ( x1 - x0 ) * ( y1 - y0 ) ; }

Returns the area of the intersection of two rectangles.

@param a Rectangle
@param b Rectangle
@return area of intersection

Returns the area of the intersection of two rectangles .


private static double get( DMatrixRMaj M , int index ) {
		if( index < 0 ) {
			return -M.data[-index-1];
		} else {
			return M.data[index-1];
		}
	}

private static double get ( DMatrixRMaj M , int index ) { if ( index < 0 ) { return - M . data [ - index - 1 ] ; } else { return M . data [ index - 1 ] ; } }

If the index is negative it returns the negative of the value at -index.  Starts at 0

If the index is negative it returns the negative of the value at - index . Starts at 0


public static Quaternion_F64 matrixToQuaternion( DMatrixRMaj R, Quaternion_F64 quat ) {

		if( quat == null )
			quat = new Quaternion_F64();

		// algorithm from:
		// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
		//
		// Designed to minimize numerical error by not dividing by very small numbers

		double m00 = R.unsafe_get(0,0);
		double m01 = R.unsafe_get(0,1);
		double m02 = R.unsafe_get(0,2);
		double m10 = R.unsafe_get(1,0);
		double m11 = R.unsafe_get(1,1);
		double m12 = R.unsafe_get(1,2);
		double m20 = R.unsafe_get(2,0);
		double m21 = R.unsafe_get(2,1);
		double m22 = R.unsafe_get(2,2);

		double trace = m00 + m11 + m22;

		if (trace > 0) {
			double S = Math.sqrt(trace+1.0) * 2; // S=4*qw
			quat.w = 0.25 * S;
			quat.x = (m21 - m12) / S;
			quat.y = (m02 - m20) / S;
			quat.z = (m10 - m01) / S;
		} else if ((m00 > m11)&(m00 > m22)) {
			double S = Math.sqrt(1.0 + m00 - m11 - m22) * 2; // S=4*qx
			quat.w = (m21 - m12) / S;
			quat.x = 0.25 * S;
			quat.y = (m01 + m10) / S;
			quat.z = (m02 + m20) / S;
		} else if (m11 > m22) {
			double S = Math.sqrt(1.0 + m11 - m00 - m22) * 2; // S=4*qy
			quat.w = (m02 - m20) / S;
			quat.x = (m01 + m10) / S;
			quat.y = 0.25 * S;
			quat.z = (m12 + m21) / S;
		} else {
			double S = Math.sqrt(1.0 + m22 - m00 - m11) * 2; // S=4*qz
			quat.w = (m10 - m01) / S;
			quat.x = (m02 + m20) / S;
			quat.y = (m12 + m21) / S;
			quat.z = 0.25 * S;
		}

		return quat;
	}

Extracts quaternions from the provided rotation matrix.

@param R (Input) rotation matrix
@param quat (Output) Optional storage for quaternion.  If null a new class will be used.
@return unit quaternion representation of the rotation matrix.

Extracts quaternions from the provided rotation matrix .


public static DMatrixRMaj rotX( double ang, DMatrixRMaj R ) {
		if( R == null )
			R = new DMatrixRMaj( 3, 3 );

		setRotX( ang, R );

		return R;
	}

public static DMatrixRMaj rotX ( double ang , DMatrixRMaj R ) { if ( R == null ) R = new DMatrixRMaj ( 3 , 3 ) ; setRotX ( ang , R ) ; return R ; }

Creates a rotation matrix about the x-axis.

@param ang the angle it rotates a point by in radians.
@param R (Output) Optional storage for rotation matrix.  Modified.
@return The 3 by 3 rotation matrix.

Creates a rotation matrix about the x - axis .


public static void setRotX( double ang, DMatrixRMaj R ) {
		double c = Math.cos( ang );
		double s = Math.sin( ang );

		R.set( 0, 0, 1 );
		R.set( 1, 1, c );
		R.set( 1, 2, -s );
		R.set( 2, 1, s );
		R.set( 2, 2, c );
	}

public static void setRotX ( double ang , DMatrixRMaj R ) { double c = Math . cos ( ang ) ; double s = Math . sin ( ang ) ; R . set ( 0 , 0 , 1 ) ; R . set ( 1 , 1 , c ) ; R . set ( 1 , 2 , - s ) ; R . set ( 2 , 1 , s ) ; R . set ( 2 , 2 , c ) ; }

Sets the values in the specified matrix to a rotation matrix about the x-axis.

@param ang the angle it rotates a point by in radians.
@param R (Output) Storage for rotation matrix.  Modified.

Sets the values in the specified matrix to a rotation matrix about the x - axis .


public static DMatrixRMaj rotY( double ang, DMatrixRMaj R ) {
		R = checkDeclare3x3( R );

		setRotY( ang, R );

		return R;
	}

public static DMatrixRMaj rotY ( double ang , DMatrixRMaj R ) { R = checkDeclare3x3 ( R ) ; setRotY ( ang , R ) ; return R ; }

Creates a rotation matrix about the y-axis.

@param ang the angle it rotates a point by in radians.
@param R (Output) Optional storage for rotation matrix.  Modified.
@return The 3 by 3 rotation matrix.

Creates a rotation matrix about the y - axis .


public static DMatrixRMaj rotZ( double ang, DMatrixRMaj R ) {
		R = checkDeclare3x3( R );

		setRotZ( ang, R );

		return R;
	}

public static DMatrixRMaj rotZ ( double ang , DMatrixRMaj R ) { R = checkDeclare3x3 ( R ) ; setRotZ ( ang , R ) ; return R ; }

Creates a rotation matrix about the z-axis.

@param ang the angle it rotates a point by in radians.
@param R (Output) Optional storage for rotation matrix.  Modified.
@return The 3 by 3 rotation matrix.

Creates a rotation matrix about the z - axis .


public static void setRotZ( double ang, DMatrixRMaj r ) {
		double c = Math.cos( ang );
		double s = Math.sin( ang );

		r.set( 0, 0, c );
		r.set( 0, 1, -s );
		r.set( 1, 0, s );
		r.set( 1, 1, c );
		r.set( 2, 2, 1 );
	}

public static void setRotZ ( double ang , DMatrixRMaj r ) { double c = Math . cos ( ang ) ; double s = Math . sin ( ang ) ; r . set ( 0 , 0 , c ) ; r . set ( 0 , 1 , - s ) ; r . set ( 1 , 0 , s ) ; r . set ( 1 , 1 , c ) ; r . set ( 2 , 2 , 1 ) ; }

Sets the values in the specified matrix to a rotation matrix about the z-axis.

@param ang the angle it rotates a point by in radians.
@param r   A 3 by 3 matrix. Is modified.

Sets the values in the specified matrix to a rotation matrix about the z - axis .


public static DMatrixRMaj eulerToMatrix( EulerType type ,
												double rotA, double rotB, double rotC,
												DMatrixRMaj R ) {
		R = checkDeclare3x3( R );

		DMatrixRMaj R_a = rotationAboutAxis( type.getAxisA(), rotA, null );
		DMatrixRMaj R_b = rotationAboutAxis( type.getAxisB(), rotB, null );
		DMatrixRMaj R_c = rotationAboutAxis( type.getAxisC(), rotC, null );

		DMatrixRMaj A = new DMatrixRMaj( 3, 3 );

		CommonOps_DDRM.mult( R_b, R_a, A );
		CommonOps_DDRM.mult( R_c, A, R );

		return R;
	}

public static DMatrixRMaj eulerToMatrix ( EulerType type , double rotA , double rotB , double rotC , DMatrixRMaj R ) { R = checkDeclare3x3 ( R ) ; DMatrixRMaj R_a = rotationAboutAxis ( type . getAxisA ( ) , rotA , null ) ; DMatrixRMaj R_b = rotationAboutAxis ( type . getAxisB ( ) , rotB , null ) ; DMatrixRMaj R_c = rotationAboutAxis ( type . getAxisC ( ) , rotC , null ) ; DMatrixRMaj A = new DMatrixRMaj ( 3 , 3 ) ; CommonOps_DDRM . mult ( R_b , R_a , A ) ; CommonOps_DDRM . mult ( R_c , A , R ) ; return R ; }

Converts an Euler coordinate into a rotation matrix.  Different type of Euler coordinates are accepted.
@param type Which Euler coordinate is the input in
@param rotA Angle of rotation around axis A.  First rotation
@param rotB Angle of rotation around axis B   Second rotation
@param rotC Angle of rotation around axis C   Third rotation
@param R (Output) Optional storage for output rotation matrix
@return Rotation matrix

Converts an Euler coordinate into a rotation matrix . Different type of Euler coordinates are accepted .


private static DMatrixRMaj rotationAboutAxis(int axis, double angle, DMatrixRMaj R ) {
		switch( axis ) {
			case 0:
				return ConvertRotation3D_F64.rotX( angle, R );

			case 1:
				return ConvertRotation3D_F64.rotY( angle, R );

			case 2:
				return ConvertRotation3D_F64.rotZ( angle, R );

			default:
				throw new IllegalArgumentException( "Unknown which" );
		}
	}

private
static
DMatrixRMaj
rotationAboutAxis
(
int
axis
,
double
angle
,
DMatrixRMaj
R
)
{
switch
(
axis
)
{
case
0
:
return
ConvertRotation3D_F64
.
rotX
(
angle
,
R
)
;
case
1
:
return
ConvertRotation3D_F64
.
rotY
(
angle
,
R
)
;
case
2
:
return
ConvertRotation3D_F64
.
rotZ
(
angle
,
R
)
;
default
:
throw
new
IllegalArgumentException
(
"Unknown which"
)
;
}
}

Creates a rotation matrix about the specified axis.

@param axis  0 = x, 1 = y, 2 = z
@param angle The angle it is rotated by in radians.
@return The 3 by 3 rotation matrix.

Creates a rotation matrix about the specified axis .


public void setAngle( double angle ) {
		slope.set( Math.cos( angle ), Math.sin( angle ) );
	}

public void setAngle ( double angle ) { slope . set ( Math . cos ( angle ) , Math . sin ( angle ) ) ; }

Sets the slope to the unit vector specified by the provided angle.

@param angle Angle of the line specified in radians.

Sets the slope to the unit vector specified by the provided angle .


public static TwistCoordinate_F64 twist( Se3_F64 motion , TwistCoordinate_F64 twist ) {
		if( twist == null )
			twist = new TwistCoordinate_F64();

		if(MatrixFeatures_DDRM.isIdentity(motion.R, GrlConstants.TEST_F64)) {
			twist.w.set(0,0,0);
			twist.v.set(motion.T);
		} else {
			Rodrigues_F64 rod = new Rodrigues_F64();
			ConvertRotation3D_F64.matrixToRodrigues(motion.R,rod);

			twist.w.set(rod.unitAxisRotation);
			double theta = rod.theta;

			// A = (I-SO)*hat(w) + w*w'*theta
			DMatrixRMaj A = CommonOps_DDRM.identity(3);
			CommonOps_DDRM.subtract(A,motion.R, A);

			DMatrixRMaj w_hat = GeometryMath_F64.crossMatrix(twist.w,null);
			DMatrixRMaj tmp = A.copy();
			CommonOps_DDRM.mult(tmp,w_hat,A);

			Vector3D_F64 w = twist.w;
			A.data[0] += w.x*w.x*theta; A.data[1] += w.x*w.y*theta; A.data[2] += w.x*w.z*theta;
			A.data[3] += w.y*w.x*theta; A.data[4] += w.y*w.y*theta; A.data[5] += w.y*w.z*theta;
			A.data[6] += w.z*w.x*theta; A.data[7] += w.z*w.y*theta; A.data[8] += w.z*w.z*theta;

			DMatrixRMaj y = new DMatrixRMaj(3,1);
			y.data[0] = motion.T.x;
			y.data[1] = motion.T.y;
			y.data[2] = motion.T.z;

			DMatrixRMaj x = new DMatrixRMaj(3,1);

			CommonOps_DDRM.solve(A,y,x);

			twist.w.scale(rod.theta);
			twist.v.x = (double) x.data[0];
			twist.v.y = (double) x.data[1];
			twist.v.z = (double) x.data[2];
			twist.v.scale(rod.theta);
		}
		return twist;
	}

public
static
TwistCoordinate_F64
twist
(
Se3_F64
motion
,
TwistCoordinate_F64
twist
)
{
if
(
twist
==
)
twist
=
new
TwistCoordinate_F64
(
)
;
if
(
MatrixFeatures_DDRM
.
isIdentity
(
motion
.
R
,
GrlConstants
.
TEST_F64
)
)
{
twist
.
w
.
set
(
0
,
0
,
0
)
;
twist
.
v
.
set
(
motion
.
T
)
;
}
else
{
Rodrigues_F64
rod
=
new
Rodrigues_F64
(
)
;
ConvertRotation3D_F64
.
matrixToRodrigues
(
motion
.
R
,
rod
)
;
twist
.
w
.
set
(
rod
.
unitAxisRotation
)
;
double
theta
=
rod
.
theta
;
// A = (I-SO)*hat(w) + w*w'*theta
DMatrixRMaj
A
=
CommonOps_DDRM
.
identity
(
3
)
;
CommonOps_DDRM
.
subtract
(
A
,
motion
.
R
,
A
)
;
DMatrixRMaj
w_hat
=
GeometryMath_F64
.
crossMatrix
(
twist
.
w
,
)
;
DMatrixRMaj
tmp
=
A
.
copy
(
)
;
CommonOps_DDRM
.
mult
(
tmp
,
w_hat
,
A
)
;
Vector3D_F64
w
=
twist
.
w
;
A
.
data
[
0
]
+=
w
.
x
*
w
.
x
*
theta
;
A
.
data
[
1
]
+=
w
.
x
*
w
.
y
*
theta
;
A
.
data
[
2
]
+=
w
.
x
*
w
.
z
*
theta
;
A
.
data
[
3
]
+=
w
.
y
*
w
.
x
*
theta
;
A
.
data
[
4
]
+=
w
.
y
*
w
.
y
*
theta
;
A
.
data
[
5
]
+=
w
.
y
*
w
.
z
*
theta
;
A
.
data
[
6
]
+=
w
.
z
*
w
.
x
*
theta
;
A
.
data
[
7
]
+=
w
.
z
*
w
.
y
*
theta
;
A
.
data
[
8
]
+=
w
.
z
*
w
.
z
*
theta
;
DMatrixRMaj
y
=
new
DMatrixRMaj
(
3
,
1
)
;
y
.
data
[
0
]
=
motion
.
T
.
x
;
y
.
data
[
1
]
=
motion
.
T
.
y
;
y
.
data
[
2
]
=
motion
.
T
.
z
;
DMatrixRMaj
x
=
new
DMatrixRMaj
(
3
,
1
)
;
CommonOps_DDRM
.
solve
(
A
,
y
,
x
)
;
twist
.
w
.
scale
(
rod
.
theta
)
;
twist
.
v
.
x
=
(
double
)
x
.
data
[
0
]
;
twist
.
v
.
y
=
(
double
)
x
.
data
[
1
]
;
twist
.
v
.
z
=
(
double
)
x
.
data
[
2
]
;
twist
.
v
.
scale
(
rod
.
theta
)
;
}
return
twist
;
}

Converts a rigid body motion into a twist coordinate.  The value of theta used to generate the motion
is assumed to be one.

@param motion (Input) The SE(3) transformation
@param twist (Output) Storage for twist.
@return magnitude of the motion

Converts a rigid body motion into a twist coordinate . The value of theta used to generate the motion is assumed to be one .


public void setTransforms( Se3_F64 initial , Se3_F64 end) {
		this.initial.set(initial);

		translation.x = end.T.x - initial.T.x;
		translation.y = end.T.y - initial.T.y;
		translation.z = end.T.z - initial.T.z;

		CommonOps_DDRM.multTransA(initial.getR(), end.getR(), R);

		ConvertRotation3D_F64.matrixToRodrigues(R,rotation);
		rotMagnitude = rotation.theta;
	}

public void setTransforms ( Se3_F64 initial , Se3_F64 end ) { this . initial . set ( initial ) ; translation . x = end . T . x - initial . T . x ; translation . y = end . T . y - initial . T . y ; translation . z = end . T . z - initial . T . z ; CommonOps_DDRM . multTransA ( initial . getR ( ) , end . getR ( ) , R ) ; ConvertRotation3D_F64 . matrixToRodrigues ( R , rotation ) ; rotMagnitude = rotation . theta ; }

Specify the two transforms which values are to be interpolated between
@param initial first transform
@param end second transform


public void interpolate( double where , Se3_F64 output ) {
		rotation.setTheta(where*rotMagnitude);
		ConvertRotation3D_F64.rodriguesToMatrix(rotation,R);

		output.T.x = initial.T.x + where*translation.x;
		output.T.y = initial.T.y + where*translation.y;
		output.T.z = initial.T.z + where*translation.z;

		CommonOps_DDRM.mult(initial.R,R,output.R);
	}

public void interpolate ( double where , Se3_F64 output ) { rotation . setTheta ( where * rotMagnitude ) ; ConvertRotation3D_F64 . rodriguesToMatrix ( rotation , R ) ; output . T . x = initial . T . x + where * translation . x ; output . T . y = initial . T . y + where * translation . y ; output . T . z = initial . T . z + where * translation . z ; CommonOps_DDRM . mult ( initial . R , R , output . R ) ; }

Interpolates a value between the first and second transform.  A value close to 0 will be
more similar to the initial and 1 more similar to the end.
@param where A value between 0 and 1 which specifies the interpolation location
@param output Resulting transform

Interpolates a value between the first and second transform . A value close to 0 will be more similar to the initial and 1 more similar to the end .


public boolean svd( List<Point3D_F64> points , Point3D_F64 outputCenter , Vector3D_F64 outputNormal ) {

		final int N = points.size();

		// find the centroid
		outputCenter.set(0,0,0);
		for( int i = 0; i < N; i++ ) {
			Point3D_F64 p = points.get(i);
			outputCenter.x += p.x;
			outputCenter.y += p.y;
			outputCenter.z += p.z;
		}

		outputCenter.x /= N;
		outputCenter.y /= N;
		outputCenter.z /= N;

		return solvePoint(points,outputCenter,outputNormal);
	}

public
boolean
svd
(
List
<
Point3D_F64
>
points
,
Point3D_F64
outputCenter
,
Vector3D_F64
outputNormal
)
{
final
int
N
=
points
.
size
(
)
;
// find the centroid
outputCenter
.
set
(
0
,
0
,
0
)
;
for
(
int
i
=
0
;
i
<
N
;
i
++
)
{
Point3D_F64
p
=
points
.
get
(
i
)
;
outputCenter
.
x
+=
p
.
x
;
outputCenter
.
y
+=
p
.
y
;
outputCenter
.
z
+=
p
.
z
;
}
outputCenter
.
x
/=
N
;
outputCenter
.
y
/=
N
;
outputCenter
.
z
/=
N
;
return
solvePoint
(
points
,
outputCenter
,
outputNormal
)
;
}

SVD based method for fitting a plane to a set of points.  The plane's equation is returned
as a point on the plane and the normal vector.

@param points (Input) Set of points on a plane.
@param outputCenter (Output) Centroid of the passed in points. Modified.
@param outputNormal (Output) Vector tangent to the plane.  Normalized.  Modified.
@return true if successful or false if it failed.

SVD based method for fitting a plane to a set of points . The plane s equation is returned as a point on the plane and the normal vector .


public boolean solvePoint(List<Point3D_F64> points , Point3D_F64 pointOnPlane , Vector3D_F64 outputNormal ) {

		final int N = points.size();

		// construct the matrix
		A.reshape(N,3);
		int index = 0;
		for( int i = 0; i < N; i++ ) {
			Point3D_F64 p = points.get(i);
			A.data[index++] = p.x - pointOnPlane.x;
			A.data[index++] = p.y - pointOnPlane.y;
			A.data[index++] = p.z - pointOnPlane.z;
		}

		// decompose and find the singular value
		if( !solverNull.process(A,1,nullspace) )
			return false;

		// the normal is the singular vector
		outputNormal.x = (double) nullspace.unsafe_get(0,0);
		outputNormal.y = (double) nullspace.unsafe_get(1,0);
		outputNormal.z = (double) nullspace.unsafe_get(2,0);

		return true;
	}

SVD based method for fitting a plane to a set of points and a known point on the plane.  The plane's
equation is returned as a point on the plane and the normal vector.

@param points (Input)Set of points on a plane.
@param pointOnPlane (Input) A known point on the plane
@param outputNormal (Output) Vector tangent to the plane.  Normalized. Modified.
@return true if successful or false if it failed.

SVD based method for fitting a plane to a set of points and a known point on the plane . The plane s equation is returned as a point on the plane and the normal vector .


public double getSideLength( int index ) {
		Point2D_F64 a = vertexes.get( index );
		Point2D_F64 b = vertexes.get( (index+1)%vertexes.size );

		return (double)a.distance(b);
	}

public double getSideLength ( int index ) { Point2D_F64 a = vertexes . get ( index ) ; Point2D_F64 b = vertexes . get ( ( index + 1 ) % vertexes . size ) ; return ( double ) a . distance ( b ) ; }

Resturns the length of the specified side that is composed of point index and index+1
@return Euclidean length of the side

Resturns the length of the specified side that is composed of point index and index + 1


public boolean isInside( Point2D_F64 p ) {
		if( isConvex() ) {
			return Intersection2D_F64.containConvex(this,p);
		} else {
			return Intersection2D_F64.containConcave(this,p);
		}
	}

public boolean isInside ( Point2D_F64 p ) { if ( isConvex ( ) ) { return Intersection2D_F64 . containConvex ( this , p ) ; } else { return Intersection2D_F64 . containConcave ( this , p ) ; } }

Returns true if the point is inside the polygon.  Points along the border are ambiguously considered inside
or outside.

@see Intersection2D_F64#containConcave(Polygon2D_F64, Point2D_F64)
@see Intersection2D_F64#containConcave(Polygon2D_F64, Point2D_F64)

@param p A point
@return true if inside and false if outside

Returns true if the point is inside the polygon . Points along the border are ambiguously considered inside or outside .