Statistics
| Branch: | Tag: | Revision:

root / src / main / scala / com / normation / cfclerk / domain / TechniqueCategory.scala @ 297687b8

History | View | Annotate | Download (6.7 kB)

1
/*
2
*************************************************************************************
3
* Copyright 2011 Normation SAS
4
*************************************************************************************
5
*
6
* This program is free software: you can redistribute it and/or modify
7
* it under the terms of the GNU Affero General Public License as
8
* published by the Free Software Foundation, either version 3 of the
9
* License, or (at your option) any later version.
10
*
11
* In accordance with the terms of section 7 (7. Additional Terms.) of
12
* the GNU Affero GPL v3, the copyright holders add the following
13
* Additional permissions:
14
* Notwithstanding to the terms of section 5 (5. Conveying Modified Source
15
* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU Affero GPL v3
16
* licence, when you create a Related Module, this Related Module is
17
* not considered as a part of the work and may be distributed under the
18
* license agreement of your choice.
19
* A "Related Module" means a set of sources files including their
20
* documentation that, without modification of the Source Code, enables
21
* supplementary functions or services in addition to those offered by
22
* the Software.
23
*
24
* This program is distributed in the hope that it will be useful,
25
* but WITHOUT ANY WARRANTY; without even the implied warranty of
26
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
* GNU Affero General Public License for more details.
28
*
29
* You should have received a copy of the GNU Affero General Public License
30
* along with this program. If not, see <http://www.gnu.org/licenses/agpl.html>.
31
*
32
*************************************************************************************
33
*/
34

    
35
package com.normation.cfclerk.domain
36

    
37
import com.normation.utils.HashcodeCaching
38
import scala.collection.SortedSet
39
import net.liftweb.common._
40

    
41
/**
42
 * A policy category name. 
43
 * It must be unique only in the context of
44
 * a parent category (i.e: all subcategories of
45
 * a given categories must have different names)
46
 */
47
case class TechniqueCategoryName(value:String) extends HashcodeCaching {
48
  require(null != value && value.length > 0, "Id of a category can not be null nor empty")
49
}
50

    
51
object RootTechniqueCategoryName extends TechniqueCategoryName("/")
52

    
53

    
54
sealed abstract class TechniqueCategoryId(val name:TechniqueCategoryName) {
55
  
56
  /**
57
   * Allows to build a subcategory with 
58
   * catId / "subCatName"
59
   */
60
  def /(subCategoryName:String) : SubTechniqueCategoryId = 
61
    SubTechniqueCategoryId(TechniqueCategoryName(subCategoryName),this)
62
  
63
  /**
64
   * The toString of a SubTechniqueCategoryId is its path
65
   * from root with "/" as separator
66
   */
67
  override lazy val toString = getPathFromRoot.tail.map( _.value ).mkString("/", "/", "")
68
  
69
  /**
70
   * The list of category from root to that one
71
   * (including that one)
72
   */
73
  lazy val getPathFromRoot = TechniqueCategoryId.pathFrom(this).reverse
74
  lazy val getIdPathFromRoot = TechniqueCategoryId.idPathFrom(this).reverse
75
  
76
  /**
77
   * The list of category from root to the
78
   * parent of that one (son excluding that one)
79
   */
80
  lazy val getParentPathFromRoot = this match {
81
    case RootTechniqueCategoryId => Nil
82
    case s:SubTechniqueCategoryId => TechniqueCategoryId.pathFrom(s)
83
  }
84
  
85
  lazy val getParentIdPathFromRoot = this match {
86
    case RootTechniqueCategoryId => Nil
87
    case s:SubTechniqueCategoryId => TechniqueCategoryId.idPathFrom(s)
88
  }
89
}
90

    
91
case object RootTechniqueCategoryId extends TechniqueCategoryId(RootTechniqueCategoryName)
92

    
93
case class SubTechniqueCategoryId(
94
    override val name: TechniqueCategoryName,
95
    parentId : TechniqueCategoryId
96
) extends TechniqueCategoryId(name) with HashcodeCaching {
97
  
98

    
99
}
100

    
101
object TechniqueCategoryId {
102
  
103
  /**
104
   * Build the path from the given TechniqueCategory
105
   * up to the root
106
   */
107
  def pathFrom(id:TechniqueCategoryId) : List[TechniqueCategoryName] = 
108
    id match {
109
      case RootTechniqueCategoryId => id.name :: Nil
110
      case SubTechniqueCategoryId(name, pId) => id.name :: pathFrom(pId)
111
    }
112

    
113
  def idPathFrom(id:TechniqueCategoryId) : List[TechniqueCategoryId] = 
114
    id match {
115
      case RootTechniqueCategoryId => id :: Nil
116
      case SubTechniqueCategoryId(name, pId) => id :: idPathFrom(pId)
117
    } 
118
  
119
    
120
  private[this] val empty = """^[\s]*$""".r
121

    
122
  /**
123
   * Build a category id from a path. 
124
   * The path must follow the unix syntaxe (a/b/c). 
125
   * If it does not start with a "slash", it is assumed that it
126
   * is relative to root so that the only difference between relative and absolute 
127
   * paths is the root. 
128
   * Trailing slashes are removed (i.e /a/b == /a/b/ == /a/b//)
129
   * Each part is checck to be non-empty (i.e: /a/b == /a//b == //a///b)
130
   * No other verification are done on the last element.
131
   * A path must contains at least one non empty element to be a valid path,
132
   * root being considered as a valid non empty element, so that :
133
   * - "/" is valid
134
   * - "/    " is valid and == "/" == "    /   / " (because in the last case, 
135
   *   root is appended to the relative path, and then all other element are empty
136
   * - "    " is valid and == "/"
137
   */
138
  def buildId(path:String) : TechniqueCategoryId = {    
139
    val absPath = "/" + path
140
    val parts = absPath.split("/").filterNot(x => empty.findFirstIn(x).isDefined)
141
    ( (RootTechniqueCategoryId:TechniqueCategoryId) /: parts) { (id,name) =>
142
        SubTechniqueCategoryId(TechniqueCategoryName(name), id)
143
    }
144
  }
145
}
146

    
147

    
148

    
149

    
150

    
151
/**
152
 * That class define a node in the hierarchy of techniques.
153
 * It's a code representation of the file system  hierarchy.
154
 *
155
 */
156
sealed trait TechniqueCategory {
157
  type A <: TechniqueCategoryId
158
  def id : A
159
  val name : String
160
  val description : String
161
  val subCategoryIds: Set[SubTechniqueCategoryId]
162
  val packageIds : SortedSet[TechniqueId]
163
  val isSystem : Boolean
164
  
165
  require(subCategoryIds.forall(sc => sc.parentId == id), 
166
      "Unconsistancy in the Technique Category; one of the subcategories is not marked as having [%s] as parent. Subcategory ids: %s".format(
167
          id.toString,
168
          subCategoryIds.map( _.toString).mkString("\n", "\n", "\n")
169
      ) )
170
}
171

    
172
case class RootTechniqueCategory(
173
  name : String,
174
  description : String,
175
  subCategoryIds: Set[SubTechniqueCategoryId] = Set(),
176
  packageIds : SortedSet[TechniqueId] = SortedSet(),
177
  isSystem : Boolean = false
178
) extends TechniqueCategory with HashcodeCaching {
179
  type A = RootTechniqueCategoryId.type
180
  override lazy val id : A = RootTechniqueCategoryId
181
}
182

    
183
case class SubTechniqueCategory(
184
  override val id : SubTechniqueCategoryId,
185
  name : String,
186
  description : String,
187
  subCategoryIds: Set[SubTechniqueCategoryId] = Set(),
188
  packageIds : SortedSet[TechniqueId] = SortedSet(),
189
  isSystem : Boolean = false
190
) extends TechniqueCategory with HashcodeCaching {
191
  type A = SubTechniqueCategoryId
192
}
193