/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2017 - ROLI Ltd. You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #pragma once //============================================================================== struct ClassDatabase { //============================================================================== struct MemberInfo { enum CodeLocationType { declaration = 0, addedToParent, setBoundsParamX, setBoundsParamY, setBoundsParamW, setBoundsParamH, // WARNING! When you change any of these, also update the copy that lives in the live editing code numCodeLocationTypes }; MemberInfo() {} MemberInfo (const MemberInfo& other) : name (other.name), type (other.type) { for (int i = 0; i < numCodeLocationTypes; ++i) locations[i] = other.locations[i]; } MemberInfo (const String& nm, const String& ty) : name (nm), type (ty) { } MemberInfo (const ValueTree& v) : name (v [Ids::name].toString()), type (v [Ids::class_].toString()) { for (int i = 0; i < numCodeLocationTypes; ++i) locations[i] = v [getIdentifierForCodeLocationType (i)].toString(); } const String& getName() const { return name; } const String& getType() const { return type; } const SourceCodeRange& getLocation (CodeLocationType t) const { return locations[t]; } void setLocation (CodeLocationType t, const SourceCodeRange& range) { locations[t] = range; } void mergeWith (const MemberInfo& other) { jassert (name == other.name); if (other.type.isNotEmpty()) type = other.type; for (int i = 0; i < numCodeLocationTypes; ++i) if (other.locations[i].isValid()) locations[i] = other.locations[i]; } void nudgeAllCodeRanges (const String& file, const int insertPoint, const int delta) { for (int i = 0; i < numCodeLocationTypes; ++i) locations[i].nudge (file, insertPoint, delta); } void fileContentChanged (const String& file) { for (int i = 0; i < numCodeLocationTypes; ++i) locations[i].fileContentChanged (file); } ValueTree toValueTree() const { ValueTree m (Ids::MEMBER); m.setProperty (Ids::name, name, nullptr); m.setProperty (Ids::class_, type, nullptr); for (int i = 0; i < numCodeLocationTypes; ++i) locations[i].writeToValueTree (m, getIdentifierForCodeLocationType (i)); return m; } private: String name, type; SourceCodeRange locations [numCodeLocationTypes]; static Identifier getIdentifierForCodeLocationType (int typeIndex) { // (These need to remain in order) static_assert (setBoundsParamX + 1 == setBoundsParamY && setBoundsParamY + 1 == setBoundsParamW && setBoundsParamW + 1 == setBoundsParamH, ""); static const Identifier ids[] = { "declaration", "addedToParent", "setBoundsParamX", "setBoundsParamY", "setBoundsParamW", "setBoundsParamH" }; return ids [typeIndex]; } }; //============================================================================== struct MethodInfo { MethodInfo() {} MethodInfo (const MethodInfo& other) : name (other.name), returnType (other.returnType), declaration (other.declaration), definition (other.definition), numArgs (other.numArgs), flags (other.flags) { } String name, returnType; SourceCodeRange declaration, definition; int numArgs, flags; enum { isConstructor = 1, isDefaultConstructor = 2, isTemplated = 4, isPublic = 8 }; MethodInfo (const ValueTree& v) : name (v[Ids::name].toString()), returnType (v[Ids::returnType].toString()), declaration (v[Ids::declaration].toString()), definition (v[Ids::definition].toString()), numArgs (v[Ids::numArgs]), flags (v[Ids::flags]) { } ValueTree toValueTree() const { ValueTree m (Ids::METHOD); m.setProperty (Ids::name, name, nullptr); m.setProperty (Ids::returnType, returnType, nullptr); m.setProperty (Ids::numArgs, numArgs, nullptr); m.setProperty (Ids::flags, flags, nullptr); declaration.writeToValueTree (m, Ids::declaration); definition.writeToValueTree (m, Ids::definition); return m; } void nudgeAllCodeRanges (const String& file, const int insertPoint, const int delta) { declaration.nudge (file, insertPoint, delta); definition.nudge (file, insertPoint, delta); } void fileContentChanged (const String& file) { declaration.fileContentChanged (file); definition.fileContentChanged (file); } }; //============================================================================== struct InstantiationFlags { InstantiationFlags() : isAbstract (false), inAnonymousNamespace (false), noDefaultConstructor (false) {} InstantiationFlags (const InstantiationFlags& other) : isAbstract (other.isAbstract), inAnonymousNamespace (other.inAnonymousNamespace), noDefaultConstructor (other.noDefaultConstructor) {} bool canBeInstantiated() const noexcept { return ! (isAbstract || inAnonymousNamespace || noDefaultConstructor); } String getReasonForUnavailability() const { if (isAbstract) return "This class is abstract"; if (noDefaultConstructor) return "This class has no default constructor"; if (inAnonymousNamespace) return "This class is declared inside an anonymous namespace"; return String(); } bool isDisallowed (const InstantiationFlags& disallowedFlags) const { return ! ((disallowedFlags.isAbstract && isAbstract) || (disallowedFlags.inAnonymousNamespace && inAnonymousNamespace) || (disallowedFlags.noDefaultConstructor && noDefaultConstructor)); } bool isAbstract; bool inAnonymousNamespace; bool noDefaultConstructor; }; //============================================================================== struct Class { Class() {} ~Class() {} Class (const Class& other) : className (other.className), members (other.members), methods (other.methods), classDeclaration (other.classDeclaration), instantiationFlags (other.instantiationFlags) { } Class (const String& name, const InstantiationFlags& flags, const Array& m, const Array& meth, const SourceCodeRange& classDeclarationRange) : className (name), members (m), methods (meth), classDeclaration (classDeclarationRange), instantiationFlags (flags) { } Class& operator= (const Class& other) { className = other.className; members = other.members; methods = other.methods; classDeclaration = other.classDeclaration; instantiationFlags = other.instantiationFlags; return *this; } const String& getName() const noexcept { return className; } const InstantiationFlags& getInstantiationFlags() const { return instantiationFlags; } void setInstantiationFlags (const InstantiationFlags& newFlags) { instantiationFlags = newFlags; } const SourceCodeRange& getClassDeclarationRange() const { return classDeclaration; } const MemberInfo* findMember (const String& memberName) const { for (auto& m : members) if (m.getName() == memberName) return &m; return nullptr; } MemberInfo* findMember (const String& memberName) { return const_cast (static_cast(*this).findMember (memberName)); } const MethodInfo* getDefaultConstructor() const { for (const MethodInfo& m : methods) if ((m.flags & MethodInfo::isDefaultConstructor) != 0) return &m; return nullptr; } const MethodInfo* getConstructor() const { if (const MethodInfo* m = getDefaultConstructor()) return m; for (const MethodInfo& m : methods) if ((m.flags & MethodInfo::isConstructor) != 0) return &m; return nullptr; } const MethodInfo* getResizedMethod() const { for (const MethodInfo& m : methods) if (m.name == "resized" && m.numArgs == 0) return &m; return nullptr; } File getMainSourceFile() const { if (const MethodInfo* m = getResizedMethod()) if (m->definition.isValid()) return m->definition.file; if (const MethodInfo* m = getConstructor()) if (m->definition.isValid()) return m->definition.file; for (auto& m : methods) if (m.definition.isValid() && File (m.definition.file).hasFileExtension ("cpp;mm")) return m.definition.file; for (auto& m : methods) if ((m.flags & MethodInfo::isConstructor) != 0 && m.definition.isValid()) return m.definition.file; for (auto& m : methods) if (m.definition.isValid() && File (m.definition.file).exists()) return m.definition.file; return {}; } Array getAllSourceFiles() const { Array files; for (const MethodInfo& m : methods) { files.addIfNotAlreadyThere (m.declaration.file); files.addIfNotAlreadyThere (m.definition.file); } return files; } bool isDeclaredInFile (const File& file) const { return file == classDeclaration.file; } void mergeWith (const Class& other) { jassert (*this == other); if (other.classDeclaration.isValid()) classDeclaration = other.classDeclaration; for (auto& m : other.members) { if (auto* existing = findMember (m.getName())) existing->mergeWith (m); else members.add (m); } } void nudgeAllCodeRanges (const String& file, int index, int delta) { for (MemberInfo& m : members) m.nudgeAllCodeRanges (file, index, delta); for (MethodInfo& m : methods) m.nudgeAllCodeRanges (file, index, delta); classDeclaration.nudge (file, index, delta); } void fileContentChanged (const String& file) { for (MemberInfo& m : members) m.fileContentChanged (file); for (MethodInfo& m : methods) m.fileContentChanged (file); classDeclaration.fileContentChanged (file); } Class (const ValueTree& v) { className = v[Ids::name]; instantiationFlags.isAbstract = v[Ids::abstract]; instantiationFlags.inAnonymousNamespace = v[Ids::anonymous]; instantiationFlags.noDefaultConstructor = v[Ids::noDefConstructor]; classDeclaration = v [Ids::classDecl].toString(); for (int i = 0; i < v.getNumChildren(); ++i) members.add (MemberInfo (v.getChild(i))); } ValueTree toValueTree() const { ValueTree v (Ids::CLASS); v.setProperty (Ids::name, className, nullptr); v.setProperty (Ids::abstract, instantiationFlags.isAbstract, nullptr); v.setProperty (Ids::anonymous, instantiationFlags.inAnonymousNamespace, nullptr); v.setProperty (Ids::noDefConstructor, instantiationFlags.noDefaultConstructor, nullptr); classDeclaration.writeToValueTree (v, Ids::classDecl); for (const MemberInfo& m : members) v.appendChild (m.toValueTree(), nullptr); return v; } bool operator== (const Class& other) const noexcept { return className == other.className; } bool operator!= (const Class& other) const noexcept { return ! operator== (other); } bool operator< (const Class& other) const noexcept { return className < other.className; } const Array& getMembers() const { return members; } private: String className; Array members; Array methods; SourceCodeRange classDeclaration; InstantiationFlags instantiationFlags; JUCE_LEAK_DETECTOR (Class) JUCE_DECLARE_WEAK_REFERENCEABLE (Class) }; //============================================================================== struct Namespace { Namespace() : name ("Global Namespace") {} Namespace (const String& n, const String& full) : name (n), fullName (full) {} bool isEmpty() const noexcept { for (const auto& n : namespaces) if (! n.isEmpty()) return false; return components.size() == 0; } int getTotalClassesAndNamespaces() const { int total = components.size(); for (const auto& n : namespaces) total += n.getTotalClassesAndNamespaces(); return total; } void add (const Class& c, const String::CharPointerType& localName) { auto nextDoubleColon = CharacterFunctions::find (localName, CharPointer_ASCII ("::")); if (nextDoubleColon.isEmpty()) merge (c); else getOrCreateNamespace (String (localName, nextDoubleColon))->add (c, nextDoubleColon + 2); } bool containsRecursively (const Class& c) const { if (components.contains (c)) return true; for (const auto& n : namespaces) if (n.containsRecursively (c)) return true; return false; } const Class* findClass (const String& className) const { for (auto& c : components) if (c.getName() == className) return &c; for (auto& n : namespaces) if (auto* c = n.findClass (className)) return c; return nullptr; } const MemberInfo* findClassMemberInfo (const String& className, const String& memberName) const { if (auto* classInfo = findClass (className)) return classInfo->findMember (memberName); return nullptr; } void findClassesDeclaredInFile (Array>& results, const File& file) { for (int i = 0; i < components.size(); ++i) { auto& c = components.getReference (i); if (c.isDeclaredInFile (file)) results.add (&c); } for (int i = 0; i < namespaces.size(); ++i) namespaces.getReference (i).findClassesDeclaredInFile (results, file); } void merge (const Namespace& other) { if (components.size() == 0) { components = other.components; } else { for (const auto& c : other.components) merge (c); } for (const auto& n : other.namespaces) getOrCreateNamespace (n.name)->merge (n); } void merge (const Class& c) { const int existing = components.indexOf (c); if (existing < 0) components.add (c); else components.getReference (existing).mergeWith (c); } Namespace* findNamespace (const String& targetName) { for (int i = 0; i < namespaces.size(); ++i) { auto& n = namespaces.getReference (i); if (n.name == targetName) return &n; } return nullptr; } Namespace* createNamespace (const String& newName) { namespaces.add (Namespace (newName, fullName + "::" + newName)); return findNamespace (newName); } Namespace* getOrCreateNamespace (const String& newName) { if (auto* existing = findNamespace (newName)) return existing; return createNamespace (newName); } void addInstantiableClasses (SortedSet& classes) const { for (const auto& c : components) if (c.getInstantiationFlags().canBeInstantiated()) classes.add (c); for (const auto& n : namespaces) n.addInstantiableClasses (classes); } void swapWith (Namespace& other) noexcept { name.swapWith (other.name); components.swapWith (other.components); namespaces.swapWith (other.namespaces); } void nudgeAllCodeRanges (const String& file, int index, int delta) { for (int i = 0; i < components.size(); ++i) components.getReference (i).nudgeAllCodeRanges (file, index, delta); for (int i = 0; i < namespaces.size(); ++i) namespaces.getReference (i).nudgeAllCodeRanges (file, index, delta); } void fileContentChanged (const String& file) { for (int i = 0; i < components.size(); ++i) components.getReference (i).fileContentChanged (file); for (int i = 0; i < namespaces.size(); ++i) namespaces.getReference (i).fileContentChanged (file); } bool matches (const Namespace& other) const { if (name == other.name && components == other.components && namespaces.size() == other.namespaces.size()) { for (int i = namespaces.size(); --i >= 0;) if (! namespaces.getReference (i).matches (other.namespaces.getReference (i))) return false; return true; } return false; } void getAllClassNames (StringArray& results, const InstantiationFlags& disallowedFlags) const { for (const auto& c : components) if (c.getInstantiationFlags().isDisallowed (disallowedFlags)) results.add (c.getName()); for (const auto& n : namespaces) n.getAllClassNames (results, disallowedFlags); } ValueTree toValueTree() const { ValueTree v (Ids::CLASSLIST); v.setProperty (Ids::name, name, nullptr); for (const auto& c : components) v.appendChild (c.toValueTree(), nullptr); for (const auto& n : namespaces) v.appendChild (n.toValueTree(), nullptr); return v; } void loadFromValueTree (const ValueTree& v) { name = v[Ids::name]; for (int i = 0; i < v.getNumChildren(); ++i) { const ValueTree c (v.getChild(i)); if (c.hasType (Ids::CLASS)) components.add (Class (c)); else if (c.hasType (Ids::CLASSLIST)) createNamespace (c[Ids::name])->loadFromValueTree (c); } } bool operator== (const Namespace& other) const noexcept { return name == other.name; } bool operator!= (const Namespace& other) const noexcept { return ! operator== (other); } bool operator< (const Namespace& other) const noexcept { return name < other.name; } String name, fullName; SortedSet components; SortedSet namespaces; JUCE_LEAK_DETECTOR (Namespace) }; struct ClassList { ClassList() {} void clear() { Namespace newNamespace; globalNamespace.swapWith (newNamespace); } void registerComp (const Class& comp) { globalNamespace.add (comp, comp.getName().getCharPointer()); } void merge (const ClassList& other) { globalNamespace.merge (other.globalNamespace); } void swapWith (ClassList& other) noexcept { globalNamespace.swapWith (other.globalNamespace); } //============================================================================== ValueTree toValueTree() const { return globalNamespace.toValueTree(); } static ClassList fromValueTree (const ValueTree& v) { ClassList l; l.globalNamespace.loadFromValueTree (v); return l; } Namespace globalNamespace; bool operator== (const ClassList& other) const noexcept { return globalNamespace.matches (other.globalNamespace); } bool operator!= (const ClassList& other) const noexcept { return ! operator== (other); } private: JUCE_LEAK_DETECTOR (ClassList) }; };